Don't use the SynchronisationProtocol for startup state changes at all.

It is now totally optional, and only estimates the latency between the
server and all clients (and this value is not even used).
This commit is contained in:
hiker 2016-11-25 22:17:24 +11:00
parent b1afac23a7
commit 6053ad207f
12 changed files with 155 additions and 191 deletions

View File

@ -28,6 +28,7 @@
#include "karts/abstract_kart.hpp"
#include "modes/world.hpp"
#include "network/network_config.hpp"
#include "network/protocols/client_lobby.hpp"
#include "network/protocols/server_lobby.hpp"
#include "network/race_event_manager.hpp"
#include "tracks/track.hpp"
@ -52,7 +53,6 @@ WorldStatus::WorldStatus()
if (device->getTimer()->isStopped())
device->getTimer()->start();
m_ready_to_race = false;
} // WorldStatus
//-----------------------------------------------------------------------------
@ -94,9 +94,9 @@ void WorldStatus::reset()
// Set the right music
World::getWorld()->getTrack()->startMusic();
// In case of a networked race the race can only start once
// all protocols are up. This flag waits for that, and is
// set by
m_ready_to_race = !NetworkConfig::get()->isNetworking();
// all protocols are up. This flag is used to wait for
// a confirmation before starting the actual race.
m_server_is_ready = false;
} // reset
//-----------------------------------------------------------------------------
@ -180,10 +180,6 @@ void WorldStatus::update(float dt)
*/
void WorldStatus::updateTime(const float dt)
{
// In case of a networked race wait till all necessary protocols are
// ready before progressing the timer
// if (!m_ready_to_race) return;
switch (m_phase)
{
// Note: setup phase must be a separate phase, since the race_manager
@ -244,47 +240,31 @@ void WorldStatus::updateTime(const float dt)
m_prestart_sound->play();
// In a networked game the client needs to wait for a notification
// from the server that ready-set-go can start now. So client will go
// to the 'wait_for_server_phase', from which they will progress once
// the notification is received. In all other cases (no networking or
// server), immediately go to race start
if (NetworkConfig::get()->isNetworking())
{
m_phase = WAIT_FOR_SERVER_PHASE;
}
else
{
m_phase = READY_PHASE;
startEngines();
}
// from the server that all clients and the server are ready to
// start the game. The server will actually wait for all clients
// to confirm that they have started the race before starting
// itself. In a normal race, this phase is skipped and the race
// starts immediately.
m_phase = NetworkConfig::get()->isNetworking() ? WAIT_FOR_SERVER_PHASE
: READY_PHASE;
return; // Don't increase time
case WAIT_FOR_SERVER_PHASE:
// On a client this phase waits for the server to be ready. On a
// server or in case of non-networked game this phase is reached
// with m_server_is_ready set to true, so it will immediately
// start the engines and then the race
if(!m_server_is_ready) return;
{
// This stage is only reached in case of a networked game.
// A client waits for a message from the server that it can
// start the race (i.e. that all clients and the server have
// loaded the world). The server waits for a confirmation from
// each client that they have started (to guarantee that the
// server is running with a local time behind all clients).
if (!m_server_is_ready) return;
if (NetworkConfig::get()->isNetworking() &&
NetworkConfig::get()->isClient())
{
// Each client informs the server when its starting the race.
// The server waits for the last message (i.e. from the slowest
// client) before starting, which means the server's local time
// will always be behind any client's time by the latency to
// that client. This in turn should guarantee that any message
// from the clients reach the server before the server actually
// needs it. By running the server behind the clients the need
// for rollbacks on the server is greatly reduced.
//RaceEventManager::getInstance()->clientHasStarted();
}
m_phase = READY_PHASE;
// Receiving a 'startReadySetGo' message from the server triggers
// a call to startReadySetGo() here, which will change the phase
// (or state) of the finite state machine.
m_phase = READY_PHASE;
Protocol *p = LobbyProtocol::get();
ClientLobby *cl = dynamic_cast<ClientLobby*>(p);
if (cl)
cl->startingRaceNow();
return; // Don't increase time
}
case READY_PHASE:
startEngines();

View File

@ -94,9 +94,6 @@ protected:
/** If the start race should be played, disabled in cutscenes. */
bool m_play_racestart_sounds;
/** A flag that causes the world to wait in case of a networking race
* till all protocols are up and running. */
bool m_ready_to_race;
private:
/** Sound to play at the beginning of a race, during which a
* a camera intro of the track can be shown. */
@ -130,10 +127,10 @@ private:
bool m_engines_started;
void startEngines();
/** In networked game the client must wait for the server to start 'ready set go'
* (to guarantee that the client's time is not ahead of the server), This flag
* indicates that the notification from the server was received, and that the
* client can go to 'ready' phase. */
/** In networked game a client must wait for the server to start 'ready
* set go' to make sure all client are actually ready to start the game.
* A server on the other hand will run behind all clients, so it will
* wait for all clients to indicate that they have started the race. */
bool m_server_is_ready;
public:

View File

@ -24,6 +24,7 @@
#include "network/network_config.hpp"
#include "network/network_player_profile.hpp"
#include "network/protocol_manager.hpp"
#include "network/protocols/synchronization_protocol.hpp"
#include "network/race_event_manager.hpp"
#include "network/stk_host.hpp"
#include "network/stk_peer.hpp"
@ -316,6 +317,10 @@ void ClientLobby::update(float dt)
NetworkKartSelectionScreen::getInstance();
screen->push();
m_state = SELECTING_KARTS;
Protocol *p = new SynchronizationProtocol();
p->requestStart();
Log::info("LobbyProtocol", "SynchronizationProtocol started.");
}
break;
case SELECTING_KARTS:
@ -585,7 +590,7 @@ void ClientLobby::kartSelectionUpdate(Event* event)
//-----------------------------------------------------------------------------
/*! \brief Called when the server broadcasts the race start.
/*! \brief Called when the server broadcasts to start the race.
race needs to be started.
* \param event : Event providing the information (no additional information
* in this case).
@ -593,12 +598,29 @@ race needs to be started.
void ClientLobby::startGame(Event* event)
{
m_state = PLAYING;
// Triggers the world finite state machine to go from WAIT_FOR_SERVER_PHASE
// to READY_PHASE.
World::getWorld()->setReadyToRace();
Log::info("ClientLobby", "Starting new game");
} // startGame
//-----------------------------------------------------------------------------
/** Called from WorldStatus when reaching the READY phase, i.e. when the race
* was started. It is going to inform the server of the race start. This
* allows the server to wait for all clients to start, so the server will
* be running behind the client with the biggest latency, which should
* make it likely that at local time T on the server all messages from
* all clients at their local time T have arrived.
*/
void ClientLobby::startingRaceNow()
{
NetworkString *ns = getNetworkString(2);
ns->addUInt8(LE_STARTED_RACE);
sendToServer(ns, /*reliable*/true);
terminateSynchronizationProtocol();
} // startingRaceNow
//-----------------------------------------------------------------------------
/*! \brief Called when the kart selection starts.
* \param event : Event providing the information (no additional information
* in this case).

View File

@ -64,6 +64,7 @@ public:
void voteReversed(uint8_t player_id, bool reversed, uint8_t track_nb = 0);
void voteLaps(uint8_t player_id, uint8_t laps, uint8_t track_nb = 0);
void doneWithResults();
void startingRaceNow();
void leave();
virtual bool notifyEvent(Event* event) OVERRIDE;

View File

@ -40,7 +40,6 @@ GameEventsProtocol::~GameEventsProtocol()
void GameEventsProtocol::setup()
{
m_count_ready_clients = 0;
World::getWorld()->setReadyToRace();
} // setup
// ----------------------------------------------------------------------------

View File

@ -23,6 +23,7 @@
#include "input/device_manager.hpp"
#include "modes/world.hpp"
#include "network/network_player_profile.hpp"
#include "network/protocol_manager.hpp"
#include "network/protocols/controller_events_protocol.hpp"
#include "network/protocols/game_events_protocol.hpp"
#include "network/protocols/kart_update_protocol.hpp"
@ -57,10 +58,6 @@ void LobbyProtocol::loadWorld()
{
Log::info("LobbyProtocol", "Ready !");
Protocol *p = new SynchronizationProtocol();
p->requestStart();
Log::info("LobbyProtocol", "SynchronizationProtocol started.");
// Race startup sequence
// ---------------------
// This creates the network world.
@ -133,3 +130,14 @@ void LobbyProtocol::loadWorld()
} // loadWorld
// ----------------------------------------------------------------------------
/** Terminates the SynchronizationProtocol.
*/
void LobbyProtocol::terminateSynchronizationProtocol()
{
Protocol *p = ProtocolManager::getInstance()
->getProtocol(PROTOCOL_SYNCHRONIZATION);
SynchronizationProtocol *sp = dynamic_cast<SynchronizationProtocol*>(p);
if (sp)
sp->requestTerminate();
} // stopSynchronizationProtocol

View File

@ -47,7 +47,8 @@ public:
LE_PLAYER_DISCONNECTED, // Client disconnected
LE_CLIENT_LOADED_WORLD, // Client finished loading world
LE_LOAD_WORLD, // Clients should load world
LE_START_RACE, // start race
LE_START_RACE, // Server to client to start race
LE_STARTED_RACE, // Client to server that it has started race
LE_START_SELECTION, // inform client to start selection
LE_RACE_FINISHED, // race has finished, display result
LE_RACE_FINISHED_ACK, // client went back to lobby
@ -94,6 +95,7 @@ public:
virtual void update(float dt) = 0;
virtual void finishedLoadingWorld() = 0;
virtual void loadWorld();
void terminateSynchronizationProtocol();
virtual void requestKartSelection(uint8_t player_id,
const std::string &kart_name)
{

View File

@ -58,12 +58,15 @@
"SELECTING" -> "playerVoteTrack" [label="Client selected track"]
"playerVoteTrack" -> "SELECTING" [label="Not all clients have selected"]
"playerVoteTrack" -> "LOAD_WORLD" [label="All clients have selected"]
"LOAD"WORLD -> "WAIT_FOR_WORLD_LOADED"
"LOAD_WORLD" -> "WAIT_FOR_WORLD_LOADED"
"WAIT_FOR_WORLD_LOADED" -> "WAIT_FOR_WORLD_LOADED" [label="Client or server loaded world"]
"WAIT_FOR_WORLD_LOADED" -> "startRace" [label="All clients and server ready"]
"startRace" -> "DELAY_SERVER"
"WAIT_FOR_WORLD_LOADED" -> "signalRaceStartToClients" [label="All clients and server ready"]
"signalRaceStartToClients" -> "WAIT_FOR_RACE_STARTED"
"WAIT_FOR_RACE_STARTED" -> "startedRaceOnClient"
"startedRaceOnClient" -> "WAIT_FOR_RACE_STARTED" [label="Not all clients have started"]
"startedRaceOnClient" -> "DELAY_SERVER" [label="All clients have started"]
"DELAY_SERVER" -> "DELAY_SERVER" [label="Not done waiting"]
"DELAY_SERVER -> "Start Countdown" [label="Server starts race now"]
"DELAY_SERVER" -> "Start Countdown" [label="Server starts race now"]
"Start Countdown" -> "Wait For Clients"
"Wait For Clients" -> "Wait For Clients" [label="Client started 'ready set go'"]
"Wait For Clients" -> "Additional Server Delay"
@ -107,7 +110,6 @@ void ServerLobby::setup()
// the server are ready:
m_player_states.clear();
m_client_ready_count.setAtomic(0);
m_server_delay = 0.02f;
m_server_has_loaded_world = false;
const std::vector<NetworkPlayerProfile*> &players =
m_game_setup->getPlayers();
@ -137,6 +139,7 @@ bool ServerLobby::notifyEventAsynchronous(Event* event)
case LE_REQUEST_BEGIN: startSelection(event); break;
case LE_KART_SELECTION: kartSelectionRequested(event); break;
case LE_CLIENT_LOADED_WORLD: finishedLoadingWorldClient(event); break;
case LE_STARTED_RACE: startedRaceOnClient(event); break;
case LE_VOTE_MAJOR: playerMajorVote(event); break;
case LE_VOTE_RACE_COUNT: playerRaceCountVote(event); break;
case LE_VOTE_MINOR: playerMinorVote(event); break;
@ -172,19 +175,19 @@ void ServerLobby::update(float dt)
requestPause();
break;
case GETTING_PUBLIC_ADDRESS:
{
Log::debug("ServerLobby", "Public address known.");
// Free GetPublicAddress protocol
delete m_current_protocol;
{
Log::debug("ServerLobby", "Public address known.");
// Free GetPublicAddress protocol
delete m_current_protocol;
// Register this server with the STK server. This will block
// this thread, but there is no need for the protocol manager
// to react to any requests before the server is registered.
registerServer();
Log::info("ServerLobby", "Server registered.");
m_state = ACCEPTING_CLIENTS;
}
break;
// Register this server with the STK server. This will block
// this thread, but there is no need for the protocol manager
// to react to any requests before the server is registered.
registerServer();
Log::info("ServerLobby", "Server registered.");
m_state = ACCEPTING_CLIENTS;
}
break;
case ACCEPTING_CLIENTS:
{
// Only poll the STK server if this is a WAN server.
@ -194,7 +197,7 @@ void ServerLobby::update(float dt)
}
case SELECTING:
// The function playerVoteTrack will trigger the next state
// one all track votes have been received.
// once all track votes have been received.
break;
case LOAD_WORLD:
Log::info("ServerLobbyRoom", "Starting the race loading.");
@ -211,14 +214,22 @@ void ServerLobby::update(float dt)
{
signalRaceStartToClients();
}
// Initialise counter again, to wait for all clients to indicate that
// they have started the race/
m_server_delay = 0.02f;
m_client_ready_count.getData() = 0;
m_client_ready_count.unlock();
break;
case WAIT_FOR_RACE_STARTED:
// The function finishedLoadingWorldClient() will trigger the
// next state.
break;
case DELAY_SERVER:
m_server_delay -= dt;
if (m_server_delay < 0)
{
m_state = RACING;
World::getWorld()->setReadyToRace();
}
break;
case RACING:
@ -319,7 +330,7 @@ void ServerLobby::signalRaceStartToClients()
ns->addUInt8(LE_START_RACE);
sendMessageToPeersChangingToken(ns, /*reliable*/true);
delete ns;
m_state = DELAY_SERVER;
m_state = WAIT_FOR_RACE_STARTED;
} // startGame
//-----------------------------------------------------------------------------
@ -328,6 +339,13 @@ void ServerLobby::signalRaceStartToClients()
*/
void ServerLobby::startSelection(const Event *event)
{
if (m_state != ACCEPTING_CLIENTS)
{
Log::warn("ServerLobby",
"Received startSelection while being in state %d", m_state);
return;
}
if(event && !event->getPeer()->isAuthorised())
{
Log::warn("ServerLobby",
@ -346,6 +364,11 @@ void ServerLobby::startSelection(const Event *event)
m_state = SELECTING;
WaitingForOthersScreen::getInstance()->push();
Protocol *p = new SynchronizationProtocol();
p->requestStart();
Log::info("LobbyProtocol", "SynchronizationProtocol started.");
} // startSelection
//-----------------------------------------------------------------------------
@ -862,28 +885,39 @@ void ServerLobby::finishedLoadingWorldClient(Event *event)
m_client_ready_count.unlock();
return;
}
Log::info("ServerLobbyeProtocol", "One of the players is ready.");
Log::info("ServerLobbyeProtocol", "Player %d is ready (%d/%d).",
player_id, m_client_ready_count.getData(),
m_game_setup->getPlayerCount() );
m_player_states[player_id] = true;
m_client_ready_count.getData()++;
}
m_client_ready_count.unlock();
if (m_client_ready_count.getAtomic() == m_game_setup->getPlayerCount())
{
Protocol *p = ProtocolManager::getInstance()
->getProtocol(PROTOCOL_SYNCHRONIZATION);
SynchronizationProtocol* protocol =
dynamic_cast<SynchronizationProtocol*>(p);
if (protocol)
{
protocol->startCountdown(5.0f); // 5 seconds countdown
Log::info("ServerLobbyProtocol",
"All players ready, starting countdown.");
}
} // if m_ready_count == number of players
} // finishedLoadingWorldClient
//-----------------------------------------------------------------------------
/** Called when a notification from a client is received that the client has
* started the race. Once all clients have informed the server that they
* have started the race, the server can start. This makes sure that the
* server's local time is behind all clients by (at least) their latency,
* which in turn means that when the server simulates local time T all
* messages from clients at their local time T should have arrived at
* the server, which creates smoother play experience.
*/
void ServerLobby::startedRaceOnClient(Event *event)
{
m_client_ready_count.lock();
Log::verbose("ServerLobby", "Host %d has started race.",
event->getPeer()->getHostId());
m_client_ready_count.getData()++;
if (m_client_ready_count.getData() == m_game_setup->getPlayerCount())
{
m_state = DELAY_SERVER;
terminateSynchronizationProtocol();
}
m_client_ready_count.unlock();
} // startedRaceOnClient
//-----------------------------------------------------------------------------
/** Called when a client clicks on 'ok' on the race result screen.
* If all players have clicked on 'ok', go back to the lobby.

View File

@ -18,6 +18,7 @@ private:
SELECTING, // kart, track, ... selection started
LOAD_WORLD, // Server starts loading world
WAIT_FOR_WORLD_LOADED, // Wait for clients and server to load world
WAIT_FOR_RACE_STARTED, // Wait for all clients to have started the race
START_RACE, // Inform clients to start race
DELAY_SERVER, // Additional server delay
RACING, // racing
@ -69,6 +70,7 @@ private:
void playerFinishedResult(Event *event);
void registerServer();
void finishedLoadingWorldClient(Event *event);
void startedRaceOnClient(Event *event);
public:
ServerLobby();
virtual ~ServerLobby();

View File

@ -7,7 +7,16 @@
#include "utils/time.hpp"
//-----------------------------------------------------------------------------
/** This protocol tries to determine the average latency between client and
* server. While this information is not used atm, it might be useful for
* the server to determine how much behind the clients it should start.
* FIXME: ATM the main thread will load the world as part of an update
* of the ProtocolManager (which updates the protocols). Since all protocols
* are locked dusing this update, the synchronisation protocol is actually
* delayed from starting while world is loading (since finding the protocol
* for a message requires the protocol lock) - causing at least two frames
* of significanlty delayed pings :(
*/
SynchronizationProtocol::SynchronizationProtocol()
: Protocol(PROTOCOL_SYNCHRONIZATION)
{
@ -17,8 +26,6 @@ SynchronizationProtocol::SynchronizationProtocol()
m_total_diff.resize(size, 0);
m_average_ping.resize(size, 0);
m_pings_count = 0;
m_countdown_activated = false;
m_last_time = -1;
} // SynchronizationProtocol
//-----------------------------------------------------------------------------
@ -30,15 +37,13 @@ SynchronizationProtocol::~SynchronizationProtocol()
void SynchronizationProtocol::setup()
{
Log::info("SynchronizationProtocol", "Ready !");
m_countdown = 5.0; // init the countdown to 5s
m_has_quit = false;
} // setup
//-----------------------------------------------------------------------------
/** Called when receiving a message. On the client side the message is a ping
* from the server, which is answered back. The client will also check if the
* server has started the countdown (which is indicated in the ping message).
* On the server the received message is a reply to a previous ping request.
* The server will keep track of average latency.
* from the server, which is answered back. On the server the received message
* is a reply to a previous ping request. The server will keep track of
* average latency.
*/
bool SynchronizationProtocol::notifyEventAsynchronous(Event* event)
{
@ -77,23 +82,6 @@ bool SynchronizationProtocol::notifyEventAsynchronous(Event* event)
delete response;
Log::verbose("SynchronizationProtocol", "Answering sequence %u at %lf",
sequence, StkTime::getRealTime());
// countdown time in the message
if (data.size() == 4)
{
float time_to_start = data.getFloat();
Log::debug("SynchronizationProtocol",
"Request to start game in %f.", time_to_start);
if (!m_countdown_activated)
startCountdown(time_to_start);
else
{
// Adjust the time based on the value sent from the server.
m_countdown = time_to_start;
}
}
else
Log::verbose("SynchronizationProtocol", "No countdown for now.");
}
else // receive response to a ping request
{
@ -135,39 +123,14 @@ bool SynchronizationProtocol::notifyEventAsynchronous(Event* event)
void SynchronizationProtocol::asynchronousUpdate()
{
float current_time = float(StkTime::getRealTime());
if (m_countdown_activated)
{
m_countdown -= (current_time - m_last_countdown_update);
m_last_countdown_update = current_time;
Log::debug("SynchronizationProtocol",
"Update! Countdown remaining : %f", m_countdown);
if (m_countdown < 0.0 && !m_has_quit)
{
m_has_quit = true;
Log::info("SynchronizationProtocol",
"Countdown finished. Starting now.");
requestTerminate();
return;
}
} // if m_countdown_activated
if (NetworkConfig::get()->isServer() && current_time > m_last_time+1)
{
const std::vector<STKPeer*> &peers = STKHost::get()->getPeers();
for (unsigned int i = 0; i < peers.size(); i++)
{
NetworkString *ping_request =
getNetworkString(m_countdown_activated ? 9 : 5);
getNetworkString(5);
ping_request->addUInt8(1).addUInt32(m_pings[i].size());
// Server adds the countdown if it has started. This will indicate
// to the client to start the countdown as well (first time the
// message is received), or to update the countdown time.
if (m_countdown_activated)
{
ping_request->addFloat(m_countdown);
Log::debug("SynchronizationProtocol",
"CNTActivated: Countdown value : %f", m_countdown);
}
Log::verbose("SynchronizationProtocol",
"Added sequence number %u for peer %d at %lf",
m_pings[i].size(), i, StkTime::getRealTime());
@ -180,19 +143,3 @@ void SynchronizationProtocol::asynchronousUpdate()
} // if current_time > m_last_time + 0.1
} // asynchronousUpdate
//-----------------------------------------------------------------------------
/** Starts the countdown on this machine. On the server side this function
* is called from ServerLobby::finishedLoadingWorld() (when all
* players have confirmed that they are ready to play). On the client side
* this function is called from this protocol when a message from the server
* is received indicating that the countdown has to be started.
* \param ms_countdown Countdown to use in ms.
*/
void SynchronizationProtocol::startCountdown(float ms_countdown)
{
m_countdown_activated = true;
m_countdown = ms_countdown;
m_last_countdown_update = float(StkTime::getRealTime());
Log::info("SynchronizationProtocol", "Countdown started with value %f",
m_countdown);
} // startCountdown

View File

@ -18,15 +18,6 @@ private:
std::vector<uint32_t> m_successed_pings;
std::vector<double> m_total_diff;
/** True if the countdown has started, i.e. all clients have loaded
* the track and karts. */
bool m_countdown_activated;
/** The countdown timer value. */
float m_countdown;
float m_last_countdown_update;
bool m_has_quit;
/** Keeps track of last time that an update was sent. */
double m_last_time;
@ -38,13 +29,9 @@ public:
virtual bool notifyEventAsynchronous(Event* event) OVERRIDE;
virtual void setup() OVERRIDE;
virtual void asynchronousUpdate() OVERRIDE;
void startCountdown(float ms_countdown);
// ------------------------------------------------------------------------
virtual void update(float dt) OVERRIDE {}
// ------------------------------------------------------------------------
/** Returns the current countdown value. */
float getCountdown() { return m_countdown; }
}; // class SynchronizationProtocol

View File

@ -5,7 +5,6 @@
#include "modes/world.hpp"
#include "network/network_config.hpp"
#include "network/protocol_manager.hpp"
#include "network/protocols/synchronization_protocol.hpp"
#include "network/protocols/controller_events_protocol.hpp"
#include "network/protocols/game_events_protocol.hpp"
@ -22,9 +21,7 @@ RaceEventManager::~RaceEventManager()
// ----------------------------------------------------------------------------
/** In network games this update function is called instead of
* World::updateWorld(). This allow this function to postpone calling
* the worl update while the countdown from the SynchronisationProtocol is
* running.
* World::updateWorld().
*/
void RaceEventManager::update(float dt)
{
@ -33,18 +30,6 @@ void RaceEventManager::update(float dt)
if(!ProtocolManager::getInstance())
return;
SynchronizationProtocol* protocol = static_cast<SynchronizationProtocol*>(
ProtocolManager::getInstance()->getProtocol(PROTOCOL_SYNCHRONIZATION));
if (protocol) // The existance of this protocol indicates that we play online
{
Log::debug("RaceEventManager", "Countdown value is %f",
protocol->getCountdown());
if (protocol->getCountdown() > 0.0)
{
return;
}
World::getWorld()->setNetworkWorld(true);
}
World::getWorld()->updateWorld(dt);
// if the race is over