17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 15:39:20 +02:00

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.
This commit is contained in:
2026-09-07 00:19:40 +00:00
parent 0b75e701fc
commit c0dbf99f1a
10 changed files with 230 additions and 130 deletions

View File

@@ -498,8 +498,6 @@ int eu07_application::run()
//
// trivia: being client and server is possible
double frameStartTime = Timer::GetTime();
if (m_modes[m_modestack.top()]->is_command_processor())
{
// active mode is doing real calculations (e.g. drivermode)
@@ -527,22 +525,29 @@ int eu07_application::run()
// if we're slave
if (m_network && m_network->client)
{
// fetch frame info from network layer,
authoritative = m_network->client->get_next_delta(MAX_NETWORK_PER_FRAME - loop_remaining);
// use delta and commands received from master
Timer::set_delta_override(authoritative.dt);
add_to_dequemap(commands_to_exec, authoritative.commands);
// take everything the authority has sent since the last frame. the
// commands are carried out at once; the world itself runs on our own
// clock rather than replaying the server's frame times, which is what
// made it run at the wrong speed whenever the two machines drew at
// different rates, and put every buffered frame between the player and
// their own controls
authoritative = m_network->client->take_pending(commands_to_exec);
// the authority owns the timeline, so we take its step number as ours
if (authoritative.valid)
Global.simulation_tick = authoritative.tick;
// what we do to the train we run ourselves happens now, not after a
// round trip. it still goes to the server, so that everybody else sees
// it; the echo of it is dropped when it comes back
command_queue::commands_map predicted;
network::collect_predictable(local_commands, predicted);
add_to_dequemap(commands_to_exec, predicted);
// and send our local commands to master
m_network->client->send_commands(local_commands);
if (!authoritative.valid)
loop_remaining = -1;
loop_remaining = -1;
}
// if we're master
else
@@ -603,25 +608,10 @@ int eu07_application::run()
}
}
// set total delta for rendering code
double totalDelta = Timer::GetTime() - frameStartTime;
Timer::set_delta_override(totalDelta);
}
}
if (!loop_remaining)
{
// loop break forced by counter
float received = m_network->client->get_frame_counter();
float awaiting = m_network->client->get_awaiting_frames();
// TODO: don't meddle with mode progresbar
m_modes[m_modestack.top()]->set_progress(100.0f * (received - awaiting) / received);
}
else
{
m_modes[m_modestack.top()]->set_progress(0.0f, 0.0f);
}
m_modes[m_modestack.top()]->set_progress(0.0f, 0.0f);
}
else
{