The server waits now for all client to start (which means it will be

behind the clients by the maximum latency, which in turn means that
at server time T all client events at time T have arrived, so less
rollback necessary).
This commit is contained in:
hiker
2016-11-16 08:48:04 +11:00
parent 2f2940115e
commit ba600f40f2
5 changed files with 106 additions and 14 deletions

View File

@@ -253,7 +253,11 @@ void WorldStatus::updateTime(const float dt)
// Notify the clients that they can start ready-set-go
if(NetworkConfig::get()->isServer())
RaceEventManager::getInstance()->startReadySetGo();
m_server_is_ready = true;
// The server will wait in 'wait_for_server' for clients
// to be ready before starting (using the same startReadySetGo
// callback).
if(!NetworkConfig::get()->isNetworking())
m_server_is_ready = true;
} // if not networked
m_phase = WAIT_FOR_SERVER_PHASE;
return; // Don't increase time
@@ -264,6 +268,20 @@ void WorldStatus::updateTime(const float dt)
// start the engines and then the race
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;
startEngines();
@@ -272,6 +290,7 @@ void WorldStatus::updateTime(const float dt)
// (or state) of the finite state machine.
return; // Don't increase time
case READY_PHASE:
if (m_auxiliary_timer > 1.0)
{
if (m_play_ready_set_go_sounds)
@@ -432,8 +451,11 @@ void WorldStatus::updateTime(const float dt)
//-----------------------------------------------------------------------------
/** Called on the client when it receives a notification from the server that
* the server is now starting its ready-set-go phase. This function changes
* the state of the finite state machine to be ready.
* all clients (and server) are ready to start the race. The server will
* then additionally wait for all clients to report back that they are
* starting, which guarantees that the server is running far enough behind
* clients time that at server time T all events from the clients at time
* T have arrived, minimising rollback impact.
*/
void WorldStatus::startReadySetGo()
{

View File

@@ -39,6 +39,7 @@ GameEventsProtocol::~GameEventsProtocol()
*/
void GameEventsProtocol::setup()
{
m_count_ready_clients = 0;
World::getWorld()->setReadyToRace();
} // setup
@@ -63,16 +64,18 @@ bool GameEventsProtocol::notifyEvent(Event* event)
int8_t type = data.getUInt8();
switch (type)
{
case GE_ITEM_COLLECTED:
collectedItem(data); break;
case GE_KART_FINISHED_RACE:
kartFinishedRace(data); break;
case GE_START_READY_SET_GO:
receivedReadySetGo(); break;
case GE_START_READY_SET_GO:
receivedReadySetGo(); break;
case GE_CLIENT_STARTED_RSG:
receivedClientHasStarted(event); break;
case GE_ITEM_COLLECTED:
collectedItem(data); break;
case GE_KART_FINISHED_RACE:
kartFinishedRace(data); break;
default:
Log::warn("GameEventsProtocol", "Unkown message type.");
break;
default:
Log::warn("GameEventsProtocol", "Unkown message type.");
break;
}
return true;
} // notifyEvent
@@ -187,3 +190,39 @@ void GameEventsProtocol::receivedReadySetGo()
assert(NetworkConfig::get()->isClient());
World::getWorld()->startReadySetGo();
} // receivedReadySetGo
// ----------------------------------------------------------------------------
/** Called on a client when it has started its ready-set-go. The client will
* inform the server anout this. The server will wait for all clients to
* have started before it will start its own countdown.
*/
void GameEventsProtocol::clientHasStarted()
{
assert(NetworkConfig::get()->isClient());
NetworkString *ns = getNetworkString(1);
ns->setSynchronous(true);
ns->addUInt8(GE_CLIENT_STARTED_RSG);
sendToServer(ns, /*reliable*/true);
delete ns;
} // clientHasStarted
// ----------------------------------------------------------------------------
/** Called on the server when a client has signaled that it has started ready
* set go. The server waits for the last client before it starts its own
* ready set go. */
void GameEventsProtocol::receivedClientHasStarted(Event *event)
{
assert(NetworkConfig::get()->isServer());
m_count_ready_clients++;
Log::verbose("GameEvent",
"Host %d has started ready-set-go: %d out of %d done",
event->getPeer()->getHostId(), m_count_ready_clients,
STKHost::get()->getGameSetup()->getPlayerCount() );
if (m_count_ready_clients==STKHost::get()->getGameSetup()->getPlayerCount())
{
Log::verbose("GameEvent", "All %d clients have started.",
STKHost::get()->getGameSetup()->getPlayerCount());
// SIgnal the server to start now - since it is now behind the client
// times by the latency of the 'slowest' client.
World::getWorld()->startReadySetGo();
}
} // receivedClientHasStarted

View File

@@ -12,10 +12,18 @@ class GameEventsProtocol : public Protocol
private:
enum GameEventType {
GE_START_READY_SET_GO = 0x01,
GE_ITEM_COLLECTED = 0x02,
GE_KART_FINISHED_RACE = 0x03
GE_CLIENT_STARTED_RSG = 0x02,
GE_ITEM_COLLECTED = 0x03,
GE_KART_FINISHED_RACE = 0x04
}; // GameEventType
/** Count how many clients have started 'ready'. The server
* will only go to its 'ready' phase if all client shave done so.
* This means the server time is far enough behind the clients
* that at time T all client messages for time T have been
* received (short of latency spikes). */
int m_count_ready_clients;
public:
GameEventsProtocol();
virtual ~GameEventsProtocol();
@@ -24,6 +32,8 @@ public:
void collectedItem(Item* item, AbstractKart* kart);
void collectedItem(const NetworkString &ns);
void kartFinishedRace(AbstractKart *kart, float time);
void clientHasStarted();
void receivedClientHasStarted(Event *event);
void kartFinishedRace(const NetworkString &ns);
void startReadySetGo();
void receivedReadySetGo();

View File

@@ -116,6 +116,26 @@ void RaceEventManager::startReadySetGo()
protocol->startReadySetGo();
} // startReadySetGo
// ----------------------------------------------------------------------------
/** Called on a client if it has started the 'ready' phase. The clients will
* send this information to the server, and the server will wait for all
* those messages before it starts its own 'ready set go'. De-facto that
* means that the server will wait for the client with the highest latency
* before it starts. Assuming no latency peaks that means that the server will
* always have received all events for time T before it executes time T
The server waits
* for all clients to report that they
*/
void RaceEventManager::clientHasStarted()
{
// this is only called in the client
assert(NetworkConfig::get()->isClient());
GameEventsProtocol* protocol = static_cast<GameEventsProtocol*>(
ProtocolManager::getInstance()->getProtocol(PROTOCOL_GAME_EVENTS));
protocol->clientHasStarted();
} // clientHasStarted
// ----------------------------------------------------------------------------
void RaceEventManager::controllerAction(Controller* controller,
PlayerAction action, int value)
{

View File

@@ -57,6 +57,7 @@ public:
int value);
void kartFinishedRace(AbstractKart *kart, float time);
void startReadySetGo();
void clientHasStarted();
// ------------------------------------------------------------------------
/** Returns if this instance is in running state or not. */
bool isRunning() { return m_running; }