diff --git a/src/modes/world_status.cpp b/src/modes/world_status.cpp index 7fbde45e7..e6869385d 100644 --- a/src/modes/world_status.cpp +++ b/src/modes/world_status.cpp @@ -30,6 +30,7 @@ #include "network/network_config.hpp" #include "network/protocols/client_lobby.hpp" #include "network/protocols/server_lobby.hpp" +#include "network/rewind_manager.hpp" #include "network/race_event_manager.hpp" #include "tracks/track.hpp" @@ -61,6 +62,7 @@ WorldStatus::WorldStatus() void WorldStatus::reset() { m_time = 0.0f; + m_adjust_time_by = 0.0f; m_auxiliary_timer = 0.0f; m_count_up_timer = 0.0f; @@ -391,14 +393,23 @@ void WorldStatus::updateTime(const float dt) } IrrlichtDevice *device = irr_driver->getDevice(); + // If this is a client, the server might request an + // adjustment of this client's world clock (to reduce + // number of rewinds). + float actual_dt; + if (NetworkConfig::get()->isClient() && + !RewindManager::get()->isRewinding()) + actual_dt = adjustDT(dt); + else + actual_dt = dt; switch (m_clock_mode) { case CLOCK_CHRONO: if (!device->getTimer()->isStopped()) { - m_time += dt; - m_count_up_timer += dt; + m_time += actual_dt; + m_count_up_timer += actual_dt; } break; case CLOCK_COUNTDOWN: @@ -412,8 +423,8 @@ void WorldStatus::updateTime(const float dt) if (!device->getTimer()->isStopped()) { - m_time -= dt; - m_count_up_timer += dt; + m_time -= actual_dt; + m_count_up_timer += actual_dt; } if(m_time <= 0.0) @@ -427,6 +438,41 @@ void WorldStatus::updateTime(const float dt) } // switch m_phase } // update +//----------------------------------------------------------------------------- +/** If the server requests that a client's time should be adjusted, + * smoothly change the clock speed of this client to go a bit faster + * or slower till the overall adjustment was reached. + * \param dt Original time step size. + */ +float WorldStatus::adjustDT(float dt) +{ + // If request, adjust world time to go ahead (adjust>0) or + // slow down (<0). This is done in 5% of dt steps so that the + // user will not notice this. + const float FRACTION = 0.05f; // fraction of dt to be adjusted + float time_adjust; + if (m_adjust_time_by >= 0) // make it run faster + { + time_adjust = dt * FRACTION; + if (time_adjust > m_adjust_time_by) time_adjust = m_adjust_time_by; + if (m_adjust_time_by > 0) + Log::verbose("info", "At %f %f adjusting time by %f dt %f to dt %f for %f", + World::getWorld()->getTime(), StkTime::getRealTime(), + time_adjust, dt, dt + time_adjust, m_adjust_time_by); + } + else // m_adjust_time negative, i.e. will go slower + { + time_adjust = -dt * FRACTION; + if (time_adjust < m_adjust_time_by) time_adjust = m_adjust_time_by; + Log::verbose("info", "At %f %f adjusting time by %f dt %f to dt %f for %f", + World::getWorld()->getTime(), StkTime::getRealTime(), + time_adjust, dt, dt + time_adjust, m_adjust_time_by); + } + m_adjust_time_by -= time_adjust; + dt += time_adjust; + return dt; +} // adjustDT + //----------------------------------------------------------------------------- /** Called on the client when it receives a notification from the server that * all clients (and server) are ready to start the race. The server will diff --git a/src/modes/world_status.hpp b/src/modes/world_status.hpp index f98fa3a6c..9e0d18d86 100644 --- a/src/modes/world_status.hpp +++ b/src/modes/world_status.hpp @@ -103,6 +103,14 @@ private: /** The third sound to be played in ready, set, go. */ SFXBase *m_start_sound; + /** In networked game the world clock might be adjusted (without the + * player noticing), e.g. if a client causes rewinds in the server, + * that client needs to speed up to be further ahead of the server + * and so reduce the number of rollbacks. This is the amount of time + * by which the client's clock needs to be adjusted (positive or + * negative). */ + float m_adjust_time_by; + /** The clock mode: normal counting forwards, or countdown */ ClockType m_clock_mode; protected: @@ -126,13 +134,15 @@ private: float m_count_up_timer; bool m_engines_started; - void startEngines(); /** 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. */ + * 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; + float adjustDT(float dt); + void startEngines(); + public: WorldStatus(); virtual ~WorldStatus(); @@ -196,7 +206,10 @@ public: float getTimeSinceStart() const { return m_count_up_timer; } // ------------------------------------------------------------------------ void setReadyToRace() { m_server_is_ready = true; } - + // ------------------------------------------------------------------------ + /** Sets a time by which the clock should be adjusted. Used by networking + * if too many rewinds are detected. */ + void setAdjustTime(float t) { m_adjust_time_by = t; } }; // WorldStatus diff --git a/src/network/protocols/game_protocol.cpp b/src/network/protocols/game_protocol.cpp index 554602242..3b3373fb1 100644 --- a/src/network/protocols/game_protocol.cpp +++ b/src/network/protocols/game_protocol.cpp @@ -69,7 +69,7 @@ void GameProtocol::update(float dt) .addUInt32(a.m_value_l).addUInt32(a.m_value_r); } // for a in m_all_actions - // FIXME: for now send reliable + // FIXME: for now send reliable sendToServer(m_data_to_send, /*reliable*/ true); m_all_actions.clear(); } // update @@ -87,6 +87,7 @@ bool GameProtocol::notifyEventAsynchronous(Event* event) { case GP_CONTROLLER_ACTION: handleControllerAction(event); break; case GP_STATE: handleState(event); break; + case GP_ADJUST_TIME: handleAdjustTime(event); break; default: Log::error("GameProtocol", "Received unknown message type %d - ignored.", message_type); break; @@ -130,6 +131,9 @@ void GameProtocol::controllerAction(int kart_id, PlayerAction action, World::getWorld()->getTime(), action, value); } // controllerAction + +#include "utils/time.hpp" + // ---------------------------------------------------------------------------- /** Called when a controller event is receiver - either on the server from * a client, or on a client from the server. It sorts the event into the @@ -140,9 +144,16 @@ void GameProtocol::handleControllerAction(Event *event) { NetworkString &data = event->data(); uint8_t count = data.getUInt8(); + bool will_trigger_rewind = false; + float rewind_delta = 0.0f; for (unsigned int i = 0; i < count; i++) { float time = data.getFloat(); + if (time < World::getWorld()->getTime()) + { + will_trigger_rewind = true; + rewind_delta = time - World::getWorld()->getTime(); + } uint8_t kart_id = data.getUInt8(); assert(kart_id < World::getWorld()->getNumKarts()); @@ -168,10 +179,46 @@ void GameProtocol::handleControllerAction(Event *event) // Send update to all clients except the original sender. STKHost::get()->sendPacketExcept(event->getPeer(), &data, false); + if (will_trigger_rewind) + { + Log::info("GameProtocol", "At %f %f requesting time adjust of %f for host %d", + World::getWorld()->getTime(), StkTime::getRealTime(), + rewind_delta, event->getPeer()->getHostId()); + // This message from a client triggered a rewind in the server. + // To avoid this, signal to the client that it should slow down. + adjustTimeForClient(event->getPeer(), rewind_delta); + } } // if server } // handleControllerAction +// ---------------------------------------------------------------------------- +/** The server might request that a client adjusts its world clock (in order to + * reduce rewinds). This function sends a a (unreliable) message to the + * client. + * \param peer The peer that triggered the rewind. + * \param t Time that the peer needs to slowdown (<0) or sped up(>0). + */ +void GameProtocol::adjustTimeForClient(STKPeer *peer, float t) +{ + assert(NetworkConfig::get()->isServer()); + NetworkString *ns = getNetworkString(5); + ns->addUInt8(GP_ADJUST_TIME).addFloat(t); + // This message can be send unreliable, it's not critical if it doesn't + // get delivered, the server will request again later anyway. + peer->sendPacket(ns, /*reliable*/false); + delete ns; +} // adjustTimeForClient + +// ---------------------------------------------------------------------------- +/** Called on a client when the server requests an adjustment of this client's + * world clock time (in order to reduce rewinds). + */ +void GameProtocol::handleAdjustTime(Event *event) +{ + float t = event->data().getFloat(); + World::getWorld()->setAdjustTime(t); +} // handleAdjustTime // ---------------------------------------------------------------------------- /** Called by the server before assembling a new message containing the full * state of the race to be sent to a client. diff --git a/src/network/protocols/game_protocol.hpp b/src/network/protocols/game_protocol.hpp index 3f6d63e87..5f211764f 100644 --- a/src/network/protocols/game_protocol.hpp +++ b/src/network/protocols/game_protocol.hpp @@ -30,6 +30,7 @@ class BareNetworkString; class NetworkString; +class STKPeer; class GameProtocol : public Protocol , public EventRewinder @@ -39,12 +40,18 @@ private: /** The type of game events to be forwarded to the server. */ enum { GP_CONTROLLER_ACTION, - GP_STATE}; + GP_STATE, + GP_ADJUST_TIME + }; /** A network string that collects all information from the server to be sent * next. */ NetworkString *m_data_to_send; + /** The server might request that the world clock of a client is adjusted + * to reduce number of rollbacks. */ + std::vector m_adjust_time; + // Dummy data structure to save all kart actions. struct Action { @@ -61,6 +68,7 @@ private: void handleControllerAction(Event *event); void handleState(Event *event); + void handleAdjustTime(Event *event); public: GameProtocol(); virtual ~GameProtocol(); @@ -73,6 +81,7 @@ public: void startNewState(); void addState(BareNetworkString *buffer); void sendState(); + void adjustTimeForClient(STKPeer *peer, float t); virtual void undo(BareNetworkString *buffer) OVERRIDE; virtual void rewind(BareNetworkString *buffer) OVERRIDE; @@ -80,7 +89,7 @@ public: virtual void setup() OVERRIDE {}; // ------------------------------------------------------------------------ virtual void asynchronousUpdate() OVERRIDE {} - + // ------------------------------------------------------------------------ }; // class GameProtocol #endif // GAME_PROTOCOL_HPP