Refactored the event/state handling to make it somewhat faster by

avoiding creating smaller time steps on the server when events
from clients are received, and also made it easier to handle
duplicated states (i.e. server rewinds and sends a second state
for a certain time).
Still work in progress though.
This commit is contained in:
hiker 2017-02-09 09:03:15 +11:00
parent ffff62ff52
commit 69658c557b
12 changed files with 465 additions and 345 deletions

View File

@ -1,5 +1,5 @@
# Modify this file to change the last-modified date when you add/remove a file.
# This will then trigger a new cmake run automatically.
# This will then trigger a new cmake run automatically.
file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp")
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*")

View File

@ -964,7 +964,7 @@ void World::update(float dt)
PROFILER_PUSH_CPU_MARKER("World::update (sub-updates)", 0x20, 0x7F, 0x00);
WorldStatus::update(dt);
RewindManager::get()->update();
RewindManager::get()->update(dt);
PROFILER_POP_CPU_MARKER();
PROFILER_PUSH_CPU_MARKER("World::update (Kart::upate)", 0x40, 0x7F, 0x00);
@ -1025,7 +1025,6 @@ void World::update(float dt)
void World::updateTime(const float dt)
{
WorldStatus::updateTime(dt);
RewindManager::get()->setCurrentTime(getTime());
} // updateTime
// ----------------------------------------------------------------------------

View File

@ -271,7 +271,8 @@ void ProtocolManager::terminateProtocol(Protocol *protocol)
} // terminateProtocol
// ----------------------------------------------------------------------------
/** Sends the event to the corresponding protocol.
/** Sends the event to the corresponding protocol. Returns true if the event
* can be ignored, or false otherwise.
*/
bool ProtocolManager::sendEvent(Event* event)
{
@ -304,13 +305,8 @@ bool ProtocolManager::sendEvent(Event* event)
m_protocols.unlock();
if (count>0 || StkTime::getTimeSinceEpoch()-event->getArrivalTime()
>= TIME_TO_KEEP_EVENTS )
{
delete event;
return true;
}
return false;
return (count > 0 || StkTime::getTimeSinceEpoch() - event->getArrivalTime()
>= TIME_TO_KEEP_EVENTS );
} // sendEvent
// ----------------------------------------------------------------------------
@ -342,6 +338,7 @@ void ProtocolManager::update(float dt)
m_events_to_process.lock();
if (result)
{
delete *i;
i = m_events_to_process.getData().erase(i);
}
else
@ -390,6 +387,7 @@ void ProtocolManager::asynchronousUpdate()
m_events_to_process.lock();
if (result)
{
delete *i;
i = m_events_to_process.getData().erase(i);
}
else

View File

@ -33,8 +33,13 @@ void RaceEventManager::update(float dt)
// Replay all recorded events up to the current time (only if the
// timer isn't stopped, otherwise a potential rewind will trigger
// an infinite loop since world time does not increase)
if(World::getWorld()->getPhase()!=WorldStatus::IN_GAME_MENU_PHASE)
RewindManager::get()->playEventsTill(World::getWorld()->getTime());
if (World::getWorld()->getPhase() != WorldStatus::IN_GAME_MENU_PHASE)
{
// This might adjust dt - if a new state is being played, the dt is
// determined from the last state till 'now'
RewindManager::get()->playEventsTill(World::getWorld()->getTime(),
&dt);
}
World::getWorld()->updateWorld(dt);

View File

@ -30,12 +30,6 @@ RewindInfo::RewindInfo(float time, bool is_confirmed)
m_is_confirmed = is_confirmed;
} // RewindInfo
// ============================================================================
RewindInfoTime::RewindInfoTime(float time)
: RewindInfo(time, /*is_confirmed*/true)
{
} // RewindInfoTime
// ============================================================================
RewindInfoState::RewindInfoState(float time, Rewinder *rewinder,
BareNetworkString *buffer, bool is_confirmed)

View File

@ -22,6 +22,7 @@
#include "network/event_rewinder.hpp"
#include "network/network_string.hpp"
#include "network/rewinder.hpp"
#include "utils/cpp2011.hpp"
#include "utils/leak_check.hpp"
#include "utils/ptr_vector.hpp"
@ -44,7 +45,7 @@ class RewindInfo
private:
LEAK_CHECK();
/** Time when this state was taken. */
/** Time when this RewindInfo was taken. */
float m_time;
/** A confirmed event is one that was sent from the server. When
@ -65,22 +66,22 @@ public:
// ------------------------------------------------------------------------
virtual ~RewindInfo() { }
// ------------------------------------------------------------------------
/** Returns the time at which this rewind state was saved. */
/** Returns the time at which this RewindInfo was saved. */
float getTime() const { return m_time; }
// ------------------------------------------------------------------------
/** Sets if this RewindInfo is confirmed or not. */
void setConfirmed(bool b) { m_is_confirmed = b; }
// ------------------------------------------------------------------------
/** Returns if this state is confirmed. */
/** Returns if this RewindInfo is confirmed. */
bool isConfirmed() const { return m_is_confirmed; }
// ------------------------------------------------------------------------
/** If this rewind info is an event. Subclasses will overwrite this. */
/** If this RewindInfo is an event. Subclasses will overwrite this. */
virtual bool isEvent() const { return false; }
// ------------------------------------------------------------------------
/** If this rewind info is time info. Subclasses will overwrite this. */
/** If this RewindInfo is time info. Subclasses will overwrite this. */
virtual bool isTime() const { return false; }
// ------------------------------------------------------------------------
/** If this rewind info is an event. Subclasses will overwrite this. */
/** If this RewindInfo is an event. Subclasses will overwrite this. */
virtual bool isState() const { return false; }
// ------------------------------------------------------------------------
}; // RewindInfo
@ -117,26 +118,6 @@ public:
BareNetworkString *getBuffer() const { return m_buffer; }
}; // RewindInfoRewinder
// ============================================================================
class RewindInfoTime : public RewindInfo
{
private:
public:
RewindInfoTime(float time);
virtual ~RewindInfoTime() {};
// ------------------------------------------------------------------------
virtual bool isTime() const { return true; }
// ------------------------------------------------------------------------
/** Called when going back in time to undo any rewind information.
* Does actually nothing. */
virtual void undo() {}
// ------------------------------------------------------------------------
/** Rewinds to this state. Nothing to be done for time info. */
virtual void rewind() {}
}; // class RewindInfoTime
// ============================================================================
class RewindInfoState: public RewindInfoRewinder
{

View File

@ -25,6 +25,7 @@
#include "network/protocols/game_protocol.hpp"
#include "network/rewinder.hpp"
#include "network/rewind_info.hpp"
#include "network/time_step_info.hpp"
#include "physics/physics.hpp"
#include "race/history.hpp"
#include "utils/log.hpp"
@ -99,7 +100,8 @@ void RewindManager::reset()
// ----------------------------------------------------------------------------
/** Adds an event to the rewind data. The data to be stored must be allocated
* and not freed by the caller!
* \param time Time at which the event was recorded.
* \param time Time at which the event was recorded. If time is not specified
* (or set to -1), the current world time is used.
* \param buffer Pointer to the event data.
*/
void RewindManager::addEvent(EventRewinder *event_rewinder,
@ -113,11 +115,11 @@ void RewindManager::addEvent(EventRewinder *event_rewinder,
return;
}
Log::verbose("RewindManager", "Time world %f self-event %f",
World::getWorld()->getTime(), getCurrentTime());
Log::verbose("RewindManager", "Time world %f self-event",
World::getWorld()->getTime());
if (time < 0)
time = getCurrentTime();
m_rewind_queue.addEvent(event_rewinder, buffer, confirmed, time);
time = World::getWorld()->getTime();
m_rewind_queue.addLocalEvent(event_rewinder, buffer, confirmed, time);
} // addEvent
// ----------------------------------------------------------------------------
@ -134,7 +136,7 @@ void RewindManager::addNetworkEvent(EventRewinder *event_rewinder,
m_rewind_queue.addNetworkEvent(event_rewinder, buffer, time);
Log::verbose("RewindManager", "Time world %f network-event %f",
World::getWorld()->getTime(), time);
} // addEvent
} // addNetworkEvent
// ----------------------------------------------------------------------------
/** Adds a state to the list of network rewind data. This function is
@ -144,23 +146,25 @@ void RewindManager::addNetworkEvent(EventRewinder *event_rewinder,
* \param time Time at which the event was recorded.
* \param buffer Pointer to the event data.
*/
void RewindManager::addNetworkState(int rewinder_index,
BareNetworkString *buffer, float time)
void RewindManager::addNetworkState(int rewinder_index, BareNetworkString *buffer,
float time)
{
assert(NetworkConfig::get()->isClient());
// On a client dt from a state is never used, it maintains
// its own dt information (using TimeEvents).
m_rewind_queue.addNetworkState(m_all_rewinder[rewinder_index], buffer,
time);
time, -99);
Log::verbose("RewindManager", "Time world %f network-state %f",
World::getWorld()->getTime(), time);
} // addState
World::getWorld()->getTime(), time);
} // addNetworkState
// ----------------------------------------------------------------------------
/** Determines if a new state snapshot should be taken, and if so calls all
* rewinder to do so.
* \param dt Time step size.
*/
void RewindManager::update()
void RewindManager::update(float dt)
{
if(!m_enable_rewind_manager ||
m_all_rewinder.size()==0 ||
m_is_rewinding ) return;
@ -168,44 +172,37 @@ void RewindManager::update()
float time = World::getWorld()->getTime();
m_not_rewound_time = time;
// Client doesn't save state (atm), so we need to store
// time infos to get the correct dt when rewinding
if(time - m_last_saved_state < m_state_frequency ||
NetworkConfig::get()->isClient())
// Avoid duplicating time step during ready-set-go, all with time 0
if (time > m_last_saved_state)
{
m_rewind_queue.addNewTimeStep(World::getWorld()->getTime(), dt);
}
// Clients don't save state, so they just update m_last_saved_state
// (only for the above if test) and exit.
if ( NetworkConfig::get()->isClient() ||
time - m_last_saved_state < m_state_frequency )
{
// Avoid saving a time event for the same time, which happens
// with t=0.
if (time > m_last_saved_state)
{
// No full state necessary, add a dummy entry for the time
// which increases replay precision (same time step size)
m_rewind_queue.addTimeEvent(getCurrentTime());
}
if (NetworkConfig::get()->isClient())
m_last_saved_state = time;
return;
}
// For now always create a snapshot.
if (NetworkConfig::get()->isServer())
GameProtocol::getInstance()->startNewState();
AllRewinder::const_iterator i;
for(i=m_all_rewinder.begin(); i!=m_all_rewinder.end(); ++i)
// Save state
GameProtocol::getInstance()->startNewState();
AllRewinder::const_iterator rewinder;
for(rewinder=m_all_rewinder.begin(); rewinder!=m_all_rewinder.end(); ++rewinder)
{
BareNetworkString *buffer = (*i)->saveState();
BareNetworkString *buffer = (*rewinder)->saveState();
if(buffer && buffer->size()>=0)
{
m_overall_state_size += buffer->size();
m_rewind_queue.addState(*i, buffer, /*confirmed*/true,
getCurrentTime());
if(NetworkConfig::get()->isServer())
GameProtocol::getInstance()->addState(buffer);
// Add to the previously created container
m_rewind_queue.addLocalState(*rewinder, buffer, /*confirmed*/true);
GameProtocol::getInstance()->addState(buffer);
} // size >= 0
else
delete buffer; // NULL or 0 byte buffer
}
if (NetworkConfig::get()->isServer())
GameProtocol::getInstance()->sendState();
GameProtocol::getInstance()->sendState();
Log::verbose("RewindManager", "%f allocated %ld",
World::getWorld()->getTime(), m_overall_state_size);
@ -216,8 +213,10 @@ void RewindManager::update()
// ----------------------------------------------------------------------------
/** Replays all events from the last event played till the specified time.
* \param time Up to (and inclusive) which time events will be replayed.
* \param dt Time step size. This might get adjusted if a new state has
* been received.
*/
void RewindManager::playEventsTill(float time)
void RewindManager::playEventsTill(float time, float *dt)
{
// No events, nothing to do
if (m_rewind_queue.isEmpty())
@ -240,44 +239,46 @@ void RewindManager::playEventsTill(float time)
rewindTo(rewind_time);
}
// This is necessary to avoid that rewinding an event will store the
// event again as a seemingly new event.
assert(!m_is_rewinding);
m_is_rewinding = true;
// Now play all events between time and time + dt
// Note that we need to use time and not World::getTime(), since
// the world time was potentially set back during a rewind
while(m_rewind_queue.hasMoreRewindInfo())
{
RewindInfo *ri = m_rewind_queue.getNext();
if (ri->getTime() > time)
{
m_is_rewinding = false;
return;
}
++m_rewind_queue;
TimeStepInfo *tsi = m_rewind_queue.getCurrent();
// This timestep simulates from time to time+dt
if (tsi->getTime() > time + *dt) break;
if(ri->isEvent())
ri->rewind();
else if (ri->isState() && NetworkConfig::get()->isClient())
++m_rewind_queue; // Point to next rewind info
tsi->replayAllEvents();
if (tsi->hasConfirmedState() && NetworkConfig::get()->isClient())
{
Log::warn("RewindManager",
"Client has received state in the future: at %f state %f",
World::getWorld()->getTime(), ri->getTime());
ri->rewind();
World::getWorld()->getTime(), tsi->getTime());
}
}
m_is_rewinding = false;
} // playEventsTill
// ----------------------------------------------------------------------------
/** Rewinds to the specified time.
* \param t Time to rewind to.
/** Rewinds to the specified time, then goes forward till the current
* World::getTime() is reached again: it will replay everything before
* World::getTime(), but not the events at World::getTime() (or later)/
* \param rewind_time Time to rewind to.
*/
void RewindManager::rewindTo(float rewind_time)
{
assert(!m_is_rewinding);
Log::info("rewind", "Rewinding to %f at %f", rewind_time,
StkTime::getRealTime());
Log::info("rewind", "Rewinding to %f at %f %f", rewind_time,
World::getWorld()->getTime(), StkTime::getRealTime());
history->doReplayHistory(History::HISTORY_NONE);
// Then undo the rewind infos going backwards in time
// --------------------------------------------------
m_is_rewinding = true;
@ -289,57 +290,44 @@ void RewindManager::rewindTo(float rewind_time)
float current_time = world->getTime();
// Get the (first) full state to which we have to rewind
RewindInfoState *state =
dynamic_cast<RewindInfoState*>(m_rewind_queue.getNext());
TimeStepInfo *current = m_rewind_queue.getCurrent();
// Store the time to which we have to replay to
float exact_rewind_time = state->getTime();
// Store the time to which we have to replay to,
// which can be earlier than rewind_time
float exact_rewind_time = current->getTime();
// Now start the rewind with the full state:
world->setTime(exact_rewind_time);
float local_physics_time = state->getLocalPhysicsTime();
float local_physics_time = current->getLocalPhysicsTime();
Physics::getInstance()->getPhysicsWorld()->setLocalTime(local_physics_time);
// Restore all states from the current time - the full state of a race
// will be potentially stored in several state objects. State can be NULL
// if the next event is not a state
while(state && state->getTime()==exact_rewind_time)
float dt = -1.0f;
// Restore the state, then all events for the specified time
current->replayAllStates();
// Need to exit loop if in-game menu is open, since world clock
// will not be increased while the game is paused
if (World::getWorld()->getPhase() == WorldStatus::IN_GAME_MENU_PHASE)
{
state->rewind();
++m_rewind_queue;
if(!m_rewind_queue.hasMoreRewindInfo()) break;
state = dynamic_cast<RewindInfoState*>(m_rewind_queue.getNext());
m_is_rewinding = false;
return;
}
current->replayAllStates();
// Now go forward through the list of rewind infos:
// ------------------------------------------------
// Need to exit loop if in-game menu is open, since world clock
// will not be increased while the game is paused
while( world->getTime() < current_time &&
World::getWorld()->getPhase()!=WorldStatus::IN_GAME_MENU_PHASE)
while (world->getTime() < current_time)
{
// Now handle all states and events at the current time before
// updating the world:
while (m_rewind_queue.hasMoreRewindInfo())
{
RewindInfo *ri = m_rewind_queue.getNext();
if (ri->getTime() > world->getTime()) break;
if (ri->isState())
{
// TOOD: replace the old state with a new state.
// For now just set it to confirmed
ri->setConfirmed(true);
}
else if (ri->isEvent())
{
ri->rewind();
}
++m_rewind_queue;
} // while ri->getTime() <= world->getTime()
float dt = m_rewind_queue.determineNextDT(current_time);
// Now handle all events(!) at the current time (i.e. between
// World::getTime() and World::getTime()+dt) before updating
// the world:
current->replayAllEvents();
dt = current->getDT();
world->updateWorld(dt);
#undef SHOW_ROLLBACK
#ifdef SHOW_ROLLBACK
@ -347,7 +335,11 @@ void RewindManager::rewindTo(float rewind_time)
#endif
world->updateTime(dt);
}
++m_rewind_queue;
if (!m_rewind_queue.hasMoreRewindInfo()) break;
current = m_rewind_queue.getCurrent();
} // while (world->getTime() < current_time)
m_is_rewinding = false;
Log::info("RewindManager", "Rewind from %f to %f finished at %f.",
rewind_time, World::getWorld()->getTime(),

View File

@ -106,12 +106,6 @@ private:
/** Time at which the last state was saved. */
float m_last_saved_state;
/** The current time to be used in all states/events. This is used to
* give all states and events during one frame the same time, even
* if e.g. states are saved before world time is increased, other
* events later. */
float m_current_time;
/** This stores the original World time during a rewind. It is used to
* detect if a client's local time need adjustment to reduce rewinds. */
float m_not_rewound_time;
@ -142,31 +136,15 @@ public:
// Non-static functtion declarations:
void reset();
void update();
void update(float dt);
void rewindTo(float target_time);
void playEventsTill(float time);
void playEventsTill(float time, float *dt);
void addEvent(EventRewinder *event_rewinder, BareNetworkString *buffer,
bool confirmed, float time = -1.0f);
void addNetworkEvent(EventRewinder *event_rewinder,
BareNetworkString *buffer, float time);
void addNetworkState(int rewinder_index, BareNetworkString *buffer,
float time);
// ------------------------------------------------------------------------
/** Sets the time that is to be used for all further states or events,
* and the time step size. This is necessary so that states/events before
* and after World::m_time is increased have the same time stamp.
* \param t Time.
* \param dt Time step size.
*/
void setCurrentTime(float t)
{
m_current_time = t;
} // setCurrentTime
// ------------------------------------------------------------------------
/** Returns the current time. */
float getCurrentTime() const { return m_current_time; }
// ------------------------------------------------------------------------
/** Adds a Rewinder to the list of all rewinders.
* \return true If rewinding is enabled, false otherwise.

View File

@ -21,6 +21,7 @@
#include "modes/world.hpp"
#include "network/rewind_info.hpp"
#include "network/rewind_manager.hpp"
#include "network/time_step_info.hpp"
#include <algorithm>
@ -38,13 +39,13 @@ RewindQueue::RewindQueue()
RewindQueue::~RewindQueue()
{
// Destroying the
AllRewindInfo::const_iterator i;
AllTimeStepInfo::const_iterator i;
for(i=m_rewind_info.begin(); i!=m_rewind_info.end(); ++i)
for(i=m_time_step_info.begin(); i!=m_time_step_info.end(); ++i)
{
delete *i;
}
m_rewind_info.clear();
m_time_step_info.clear();
} // ~RewindQueue
// ----------------------------------------------------------------------------
@ -52,22 +53,21 @@ RewindQueue::~RewindQueue()
*/
void RewindQueue::reset()
{
#ifdef REWIND_SEARCH_STATS
m_count_of_comparisons = 0;
m_count_of_searches = 0;
#endif
AllRewindInfo::const_iterator i;
for(i=m_rewind_info.begin(); i!=m_rewind_info.end(); i++)
for(AllTimeStepInfo::const_iterator i =m_time_step_info.begin();
i!=m_time_step_info.end(); i++)
{
delete *i;
}
m_rewind_info.clear();
m_current = m_rewind_info.begin();
m_time_step_info.clear();
m_current = m_time_step_info.begin();
m_network_events.lock();
const AllRewindInfo &info = m_network_events.getData();
for (i = info.begin(); i != info.end(); ++i)
AllNetworkRewindInfo &info = m_network_events.getData();
for (AllNetworkRewindInfo::const_iterator i = info.begin();
i != info.end(); ++i)
{
delete *i;
}
@ -75,38 +75,57 @@ void RewindQueue::reset()
m_network_events.unlock();
} // reset
// ----------------------------------------------------------------------------
/** Adds a new TimeStepInfo for the specified time. The TimeStepInfo acts
* as an container to store all states and events that happen at this time
* (or at least close to this time, since e.g. several events from clients
* happening at slightly different times will be all handled in the same
* timestep.
* \param time New time to add.
* \param dt Time step size that is going to be used for this time step.
*/
void RewindQueue::addNewTimeStep(float time, float dt)
{
TimeStepInfo *tsi = new TimeStepInfo(time, dt);
AllTimeStepInfo::iterator i = m_time_step_info.end();
while (i != m_time_step_info.begin())
{
--i;
if ((*i)->getTime() < time)
{
i++;
break;
}
};
m_time_step_info.insert(i, tsi);
} // addNewTimeStep
// ----------------------------------------------------------------------------
/** Finds the TimeStepInfo object to which an event at time t should be added.
* The TimeStepInfo object might not have the exacct same time, it can be
* the closest existing (or in future this function might even add a totally
* new TimeStepInfo object).
* \param Time at which the event that needs to be added hapened.
*/
TimeStepInfo *RewindQueue::findClosestTimeStepInfo(float t)
{
AllTimeStepInfo::reverse_iterator i = m_time_step_info.rbegin();
while (i != m_time_step_info.rend() && (*i)->getTime() > t)
i++;
return *i;
} // findClosestTimeStepInfo
// ----------------------------------------------------------------------------
/** A compare function used when sorting the event lists. It sorts events by
* time. In case of equal times, it sorts states and events first (since the
* state needs to be restored when replaying first before any other events).
*/
bool RewindQueue::_RewindInfoCompare::operator()(const RewindInfo *ri1,
const RewindInfo *ri2) const
bool RewindQueue::_TimeStepInfoCompare::operator()(const TimeStepInfo * const ri1,
const TimeStepInfo * const ri2) const
{
if (ri1->getTime() < ri2->getTime()) return true;
if (ri1->getTime() > ri2->getTime()) return false;
// Now the times are equal. In this case make sure that states are sorted
// before times and they before events. If two identical types are found,
// sort them by pointer address (to guarantee that (a,b) and (b,a) give
// consistent results (otherwise e.g. VS in debug mode will detect
// inconsistencies and throw an exception).
if (ri1->isState())
{
if (ri2->isState()) return ri1 < ri2;
return true; // state first, i.e smallest
}
if (ri1->isTime())
{
if (ri2->isState()) return false;
if (ri2->isEvent()) return true;
return ri1 < ri2;
}
// Now ri1 must be event
if (ri2->isEvent()) return ri1 < ri2;
// ri2 is state or time which must come first
return false;
return ri1->getTime() < ri2->getTime();
} // RewindQueue::operator()
// ----------------------------------------------------------------------------
@ -118,13 +137,8 @@ bool RewindQueue::_RewindInfoCompare::operator()(const RewindInfo *ri1,
*/
void RewindQueue::insertRewindInfo(RewindInfo *ri)
{
AllRewindInfo::iterator i =
std::upper_bound(m_rewind_info.begin(),
m_rewind_info.end(), ri,
RewindQueue::m_rewind_info_compare);
AllRewindInfo::iterator new_pos = m_rewind_info.insert(i, ri);
if (m_current == m_rewind_info.end())
m_current = new_pos;
TimeStepInfo *bucket = findClosestTimeStepInfo(ri->getTime());
bucket->insert(ri);
} // insertRewindInfo
// ----------------------------------------------------------------------------
@ -133,31 +147,38 @@ void RewindQueue::insertRewindInfo(RewindInfo *ri)
* \param time Time at which the event was recorded.
* \param buffer Pointer to the event data.
*/
void RewindQueue::addEvent(EventRewinder *event_rewinder,
BareNetworkString *buffer, bool confirmed,
float time )
void RewindQueue::addLocalEvent(EventRewinder *event_rewinder,
BareNetworkString *buffer, bool confirmed,
float time )
{
RewindInfo *ri = new RewindInfoEvent(time, event_rewinder,
buffer, confirmed);
insertRewindInfo(ri);
} // addEvent
} // addLocalEvent
// ----------------------------------------------------------------------------
/** Adds a state from the local simulation. It is not thread-safe, so needs to
* be called from the main thread
/** Adds a state from the local simulation to the last created TimeStepInfo
* container with the current world time. It is not thread-safe, so needs
* to be called from the main thread.
* \param rewinder The rewinder object for this state.
* \param buffer The state information.
* \param confirmed If this state is confirmed to be correct (e.g. is
* being received from the servrer), or just a local state for
* faster rewinds.
*/
void RewindQueue::addState(Rewinder *rewinder, BareNetworkString *buffer,
bool confirmed, float time)
void RewindQueue::addLocalState(Rewinder *rewinder, BareNetworkString *buffer,
bool confirmed)
{
RewindInfo *ri = new RewindInfoState(time, rewinder, buffer, confirmed);
RewindInfo *ri = new RewindInfoState(World::getWorld()->getTime(), rewinder,
buffer, confirmed);
assert(ri);
insertRewindInfo(ri);
} // addState
} // addLocalState
// ----------------------------------------------------------------------------
/** Adds an event to the list of network rewind data. This function is
* threadsafe so can be called by the network thread. The data is synched
* to m_rewind_info by the main thread. The data to be stored must be
* to m_time_step_info by the main thread. The data to be stored must be
* allocated and not freed by the caller!
* \param time Time at which the event was recorded.
* \param buffer Pointer to the event data.
@ -168,51 +189,29 @@ void RewindQueue::addNetworkEvent(EventRewinder *event_rewinder,
RewindInfo *ri = new RewindInfoEvent(time, event_rewinder,
buffer, /*confirmed*/true);
// Sort the incoming network events so that we can use list::merge
// to move the network events into the RewindInfo main list.
m_network_events.lock();
AllRewindInfo::iterator i =
std::upper_bound(m_network_events.getData().begin(),
m_network_events.getData().end() , ri,
RewindQueue::m_rewind_info_compare );
m_network_events.getData().insert(i, ri);
m_network_events.getData().push_back(ri);
m_network_events.unlock();
} // addEvent
} // addNetworkEvent
// ----------------------------------------------------------------------------
/** Adds a state to the list of network rewind data. This function is
* threadsafe so can be called by the network thread. The data is synched
* to m_rewind_info by the main thread. The data to be stored must be
* to m_time_step_info by the main thread. The data to be stored must be
* allocated and not freed by the caller!
* \param time Time at which the event was recorded.
* \param buffer Pointer to the event data.
*/
void RewindQueue::addNetworkState(Rewinder *rewinder,
BareNetworkString *buffer, float time)
void RewindQueue::addNetworkState(Rewinder *rewinder, BareNetworkString *buffer,
float time, float dt)
{
RewindInfo *ri = new RewindInfoState(time, rewinder,
buffer, /*confirmed*/true);
// Sort the incoming network events so that we can use list::merge
// to move the network events into the RewindInfo main list.
m_network_events.lock();
AllRewindInfo::iterator i =
std::upper_bound(m_network_events.getData().begin(),
m_network_events.getData().end(), ri,
RewindQueue::m_rewind_info_compare );
m_network_events.getData().insert(i, ri);
m_network_events.getData().push_back(ri);
m_network_events.unlock();
} // addState
// ----------------------------------------------------------------------------
/** Adds a (dummy) time event to store the time. This enables rewinding to
* replay using the same time step size, minimising errors.
*/
void RewindQueue::addTimeEvent(float time)
{
RewindInfo *ri = new RewindInfoTime(time);
insertRewindInfo(ri);
} // addTimeEvent
} // addNetworkState
// ----------------------------------------------------------------------------
/** Merges thread-safe all data received from the network with the current
@ -227,55 +226,46 @@ void RewindQueue::mergeNetworkData(bool *needs_rewind, float *rewind_time)
{
*needs_rewind = false;
m_network_events.lock();
if (m_rewind_info.empty() && m_network_events.getData().empty())
if(m_network_events.getData().empty())
{
m_network_events.unlock();
return;
}
/** First merge all newly received network events into the main
/** Merge all newly received network events into the main
* event list */
*rewind_time = -1.0f;
*rewind_time = 99999.9f;
bool adjust_next = false;
float world_time = World::getWorld()->getTime();
if (!m_network_events.getData().empty())
AllNetworkRewindInfo::iterator i;
for (i = m_network_events.getData().begin();
i != m_network_events.getData().end(); ++i)
{
// Check if a rewind is necessary
*rewind_time = m_network_events.getData().front()->getTime();
if (*rewind_time < World::getWorld()->getTime())
float t= (*i)->getTime();
if (t < world_time)
{
*needs_rewind = true;
if (t < *rewind_time) *rewind_time = t;
}
// It is possible that m_current points m_rewind_info.end()
// and that new elements are added to the end of the list. In this
// case m_rewind_info end would still point to end(), but should
// point to the newly added element. To achieve this, we first
// decrement m_current (note that it was tested previously that
// the list is not empty), merge in the new events, and then
// increment m_current again. If elements have been added to
// the end of the list, m_current will now corretly point to them.
adjust_next = !m_rewind_info.empty();
if (adjust_next) m_current--;
m_rewind_info.merge(m_network_events.getData(), m_rewind_info_compare);
// RewindInfoCompare::RewindInfoCompare);
if (adjust_next)
m_current++;
else // rewind_info was empty, set pointer to first element
m_current = m_rewind_info.begin();
} // if !m_network_events empty
TimeStepInfo *tsi = findClosestTimeStepInfo(t);
// FIXME We could try to find the closest TimeStepInfo, or even
// perhaps add a new TimeStepInfo if this new entry is
// 'close to the middle'.
tsi->insert(*i);
} // for i in m_network_events
m_network_events.getData().clear();
m_network_events.unlock();
} // mergeNetworkData
// ----------------------------------------------------------------------------
bool RewindQueue::isEmpty() const
{
if (m_current != m_rewind_info.end())
if (m_current != m_time_step_info.end())
return false;
m_network_events.lock();
@ -290,7 +280,7 @@ bool RewindQueue::isEmpty() const
*/
bool RewindQueue::hasMoreRewindInfo() const
{
return m_current != m_rewind_info.end();
return m_current != m_time_step_info.end();
} // hasMoreRewindInfo
// ----------------------------------------------------------------------------
@ -306,7 +296,7 @@ float RewindQueue::determineNextDT(float end_time)
{
// If there is a next state (which is known to have a different time)
// use the time difference to determine the time step size.
if(m_current !=m_rewind_info.end())
if(m_current !=m_time_step_info.end())
return (*m_current)->getTime() - World::getWorld()->getTime();
// Otherwise, i.e. we are rewinding the last state/event, take the
@ -318,44 +308,36 @@ float RewindQueue::determineNextDT(float end_time)
// ----------------------------------------------------------------------------
/** Rewinds the rewind queue and undos all events/states stored. It stops
* when the first state is reached that was recorded before the undo_time.
* It sets the internal 'current' pointer to this state.
* when the first confirmed state is reached that was recorded before the
* undo_time and sets the internal 'current' pointer to this state. It is
* assumed that this function is called after a new TimeStepInfo instance
* was added (i.e. after RewindManager::update() was called), so the state
* m_current is pointing to is ignored.
* \param undo_time To what at least events need to be undone.
*/
void RewindQueue::undoUntil(float undo_time)
{
AllRewindInfo::iterator i = m_rewind_info.end();
while (i != m_rewind_info.begin())
while (m_current != m_time_step_info.begin())
{
--i;
if ((*i)->isState() &&
(*i)->getTime() <= undo_time)
--m_current;
// Undo all events and states from the current time
(*m_current)->undoAll();
if ((*m_current)->getTime() <= undo_time &&
(*m_current)->hasConfirmedState())
{
// FIXME: we might have more than one state, so go back along
// states at the same time
m_current = i;
return;
}
// Undo the effect of the event.
(*i)->undo();
// Any states saved after the undo_time are invalid. For now mark
// them as being unconfirmed.
if ((*i)->isState() &&
(*i)->getTime() > undo_time)
{
(*i)->setConfirmed(false);
}
} // while i!=m_rewind_info.begin()
} // while m_current!=m_time_step_info.begin()
Log::error("RewindManager", "No state for rewind to %f",
undo_time);
m_current = m_rewind_info.begin();
} // undoUntil
#ifdef XXXXXXXXXXXXXXX
// ----------------------------------------------------------------------------
/** Tests the sorting order of events with the same time stamp: states must
* be first, then time info, then events.
@ -370,11 +352,11 @@ void RewindQueue::testingSortingOrderType(EventRewinder *rewinder, int types[3])
{
BareNetworkString *s = new BareNetworkString();
s->addUInt8(1);
addState(NULL, s, true, 0.0f);
addState(NULL, s, true, 0.0f, 0.1f);
break;
}
case 2:
addTimeEvent(0.0f);
addTimeEvent(0.0f, 0.1f);
break;
case 3:
{
@ -394,7 +376,7 @@ void RewindQueue::testingSortingOrderType(EventRewinder *rewinder, int types[3])
// State, then time, then event
assert(hasMoreRewindInfo());
assert(!isEmpty());
RewindInfo *ri = getNext();
RewindInfo *ri = getCurrent();
assert(ri->getTime() == 0.0f);
assert(ri->isState());
RewindInfoState *ris = dynamic_cast<RewindInfoState*>(ri);
@ -402,14 +384,14 @@ void RewindQueue::testingSortingOrderType(EventRewinder *rewinder, int types[3])
assert(ris->getBuffer()->getUInt8() == 1);
operator++();
ri = getNext();
ri = getCurrent();
assert(!isEmpty());
assert(hasMoreRewindInfo());
assert(ri->getTime() == 0.0f);
assert(ri->isTime());
operator++();
ri = getNext();
ri = getCurrent();
assert(!isEmpty());
assert(hasMoreRewindInfo());
assert(ri->getTime() == 0.0f);
@ -439,7 +421,7 @@ void RewindQueue::testingSortingOrderTime(EventRewinder *rewinder,
// Avoid warnings about 'no state found' when rewinding
// and there is indeed no state at the given time.
addState(NULL, new BareNetworkString(), true, 0);
addState(NULL, new BareNetworkString(), true, 0, 0.1f);
for (unsigned int i = 0; i<3; i++)
{
switch (types[i])
@ -448,11 +430,11 @@ void RewindQueue::testingSortingOrderTime(EventRewinder *rewinder,
{
BareNetworkString *s = new BareNetworkString();
s->addUInt8(1);
addState(NULL, s, true, times[i]);
addState(NULL, s, true, times[i], 0.1f);
break;
}
case 2:
addTimeEvent(times[i]);
addTimeEvent(times[i], 0.1f);
break;
case 3:
{
@ -469,11 +451,11 @@ void RewindQueue::testingSortingOrderTime(EventRewinder *rewinder,
// This should go back to the first state
undoUntil(min_rewind_time);
RewindInfo *ri_prev = getNext();
RewindInfo *ri_prev = getCurrent();
operator++();
while (hasMoreRewindInfo())
{
RewindInfo *ri = getNext();
RewindInfo *ri = getCurrent();
assert(ri_prev->getTime() < ri->getTime());
ri_prev = ri;
operator++();
@ -544,12 +526,21 @@ void RewindQueue::unitTesting()
// 1) Current pointer was not reset from end of list when an event
// was added and the pointer was already at end of list
RewindQueue b1;
b1.addState(NULL, new BareNetworkString(), true, 1.0f);
b1.addState(NULL, new BareNetworkString(), true, 1.0f, 0.1f);
++b1; // Should now point at end of list
b1.hasMoreRewindInfo();
b1.addEvent(NULL, new BareNetworkString(), true, 2.0f);
RewindInfo *ri = b1.getNext();
RewindInfo *ri = b1.getCurrent();
assert(ri->getTime() == 2.0f);
assert(ri->isEvent());
} // unitTesting
} // unitTesting
#endif
void RewindQueue::unitTesting()
{
}

View File

@ -27,8 +27,9 @@
#include <list>
#include <vector>
class RewindInfo;
class EventRewinder;
class RewindInfo;
class TimeStepInfo;
/** \ingroup network
*/
@ -36,38 +37,31 @@ class EventRewinder;
class RewindQueue
{
private:
/** Pointer to all saved states. */
typedef std::list<RewindInfo*> AllRewindInfo;
/** Pointer to all saved */
typedef std::list<TimeStepInfo*> AllTimeStepInfo;
/** The list of all events that are affected by a rewind. */
AllRewindInfo m_rewind_info;
AllTimeStepInfo m_time_step_info;
/** The list of all events received from the network. They are stored
* in a separate thread (so this data structure is thread-save), and
* merged into m_rewind_info from the main thread. This design (as
* opposed to locking m_rewind_info) reduces the synchronisation
* between main thread and network thread. */
Synchronised<AllRewindInfo> m_network_events;
typedef std::vector<RewindInfo*> AllNetworkRewindInfo;
Synchronised<AllNetworkRewindInfo> m_network_events;
/** Iterator to the next rewind info to be handled. */
AllRewindInfo::iterator m_current;
#define REWIND_SEARCH_STATS
#ifdef REWIND_SEARCH_STATS
/** Gather some statistics about how many comparisons we do,
* to find out if it's worth doing a binary search.*/
mutable int m_count_of_comparisons;
mutable int m_count_of_searches;
#endif
/** Iterator to the curren time step info to be handled. This should
* always be at the same time as World::getTime(). */
AllTimeStepInfo::iterator m_current;
TimeStepInfo *findClosestTimeStepInfo(float t);
void insertRewindInfo(RewindInfo *ri);
struct _RewindInfoCompare
struct _TimeStepInfoCompare
{
bool operator()(const RewindInfo *ri1, const RewindInfo *ri2) const;
} m_rewind_info_compare;
bool operator()(const TimeStepInfo * const ri1, const TimeStepInfo * const ri2) const;
} m_time_step_info_compare;
void testingSortingOrderType(EventRewinder *rewinder, int types[3]);
void testingSortingOrderTime(EventRewinder *rewinder, int types[3],
@ -79,15 +73,15 @@ public:
RewindQueue();
~RewindQueue();
void reset();
void addEvent(EventRewinder *event_rewinder, BareNetworkString *buffer,
bool confirmed, float time);
void addState(Rewinder *rewinder, BareNetworkString *buffer,
bool confirmed, float time);
void addNewTimeStep(float time, float dt);
void addLocalEvent(EventRewinder *event_rewinder, BareNetworkString *buffer,
bool confirmed, float time);
void addLocalState(Rewinder *rewinder, BareNetworkString *buffer,
bool confirmed);
void addNetworkEvent(EventRewinder *event_rewinder,
BareNetworkString *buffer, float time);
void addNetworkState(Rewinder *rewinder, BareNetworkString *buffer,
float time);
void addTimeEvent(float time);
float time, float dt);
void mergeNetworkData(bool *needs_rewind, float *rewind_time);
bool isEmpty() const;
bool hasMoreRewindInfo() const;
@ -95,19 +89,19 @@ public:
float determineNextDT(float max_time);
// ------------------------------------------------------------------------
RewindQueue::AllRewindInfo::iterator& operator++()
RewindQueue::AllTimeStepInfo::iterator& operator++()
{
assert(m_current != m_rewind_info.end());
assert(m_current != m_time_step_info.end());
m_current++;
return m_current;
} // operator++
// ------------------------------------------------------------------------
/** Returns the next event. Caller must make sure that there is at least
/** Returns the current RewindInfo. Caller must make sure that there is at least
* one more RewindInfo (see hasMoreRewindInfo()). */
RewindInfo *getNext()
TimeStepInfo *getCurrent()
{
assert(m_current != m_rewind_info.end());
assert(m_current != m_time_step_info.end());
return *m_current;
} // getNext

View File

@ -0,0 +1,96 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2013 Joerg Henrichs
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "network/time_step_info.hpp"
#include "network/rewind_info.hpp"
#include "physics/physics.hpp"
/** Creates a new TimeStepInfo for a given time and given dt.
* \param time Time for this TimeStepInfo object.
* \param dt Time step size.
*/
TimeStepInfo::TimeStepInfo(float time, float dt)
{
m_time = time;
m_dt = dt;
m_local_physics_time = Physics::getInstance()->getPhysicsWorld()
->getLocalTime();
} // StateEventList
// --------------------------------------------------------------------
/** Adds a state. State must be saved first, i.e. before events. The
* RewindManager guarantees this order
* \param ri The RewindInfo object for the state.
*/
void TimeStepInfo::insert(RewindInfo *ri)
{
if (ri->isState())
{
// States need to be inserted first.
// FIXME: handle duplicated states, e.g. server doing a rewind
// and sending another updated state
AllRewindInfo::iterator i = m_list_of_events.begin();
while (i != m_list_of_events.end() && (*i)->isState())
++i;
m_list_of_events.insert(i, ri);
}
else
{
// Events at the same time are just added to the end
m_list_of_events.push_back(ri);
}
} // addStaet
// --------------------------------------------------------------------
/** Undos all events and states for this time step.
*/
void TimeStepInfo::undoAll()
{
AllRewindInfo::reverse_iterator i;
for (i = m_list_of_events.rbegin(); i != m_list_of_events.rend(); i++)
{
(*i)->undo();
}
} // undoAll
// --------------------------------------------------------------------
/** Replays all events for this TimeStepInfo.
*/
void TimeStepInfo::replayAllEvents()
{
AllRewindInfo::reverse_iterator i;
for (i = m_list_of_events.rbegin(); i != m_list_of_events.rend(); i++)
{
if ((*i)->isEvent())
(*i)->rewind();
}
} // replayAllEvents
// --------------------------------------------------------------------
/** Replays all state information for this TimeStepInfo.
*/
void TimeStepInfo::replayAllStates()
{
AllRewindInfo::reverse_iterator i;
for (i = m_list_of_events.rbegin(); i != m_list_of_events.rend(); i++)
{
if ((*i)->isState())
(*i)->rewind();
}
} // replayAllStates

View File

@ -0,0 +1,92 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 Joerg Henrichs
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_TIME_STEP_INFO_HPP
#define HEADER_TIME_STEP_INFO_HPP
#include "network/rewind_info.hpp"
#include <assert.h>
#include <vector>
class RewindInfo;
class EventRewinder;
/** \ingroup network
*/
class RewindInfo;
class RewindInfoEvent;
class RewindInfoState;
/** This class stores information about each time step on a client or server.
* Firstly it stores the world time and time step size. In case of a rewind
* this allows the rewind to use the same time step size, which reduces
* jitter caused by different time step size. Secondly for each time it
* stores (if exist) all states, and all events. The TimeStepInfo acts as a
* container and will store all states and events that happened 'around' time,
* i.e. between time-X and time+Y (X and Y are implicitely defined in the
* RewindQueue). This avoids that messages from clients to the server create
* more and more TimeStepInfo, with smaller and smaller dt, which would make
* rewinds more expensive.
*/
class TimeStepInfo
{
private:
typedef std::vector<RewindInfo*> AllRewindInfo;
/** The list of all states and events at a certain time. */
AllRewindInfo m_list_of_events;
/** Time at which those events should be executed here. */
float m_time;
/** Time step to be used. */
float m_dt;
/** Bullet maintains a 'left over' time since it is running with a fixed
* 60 fps. Restoring this value exactly improves accuracy of rewinds. */
float m_local_physics_time;
public:
TimeStepInfo(float time, float dt);
void insert(RewindInfo *ri);
void undoAll();
void replayAllEvents();
void replayAllStates();
// ------------------------------------------------------------------------
/** Returns the time for this TimeStepInfo instance. */
float getTime() const { return m_time; }
// ------------------------------------------------------------------------
/** Returns the left-over physics time. */
float getLocalPhysicsTime() const { return m_local_physics_time; }
// ------------------------------------------------------------------------
/** Returns the (previous) time step size, so that rewindw can be done
* with same time step size. */
float getDT() const { return m_dt; }
// ------------------------------------------------------------------------
/** Returns if this TimeStepInfo instance has a confirmed state, i.e. if
* a rewind can start from this time. */
bool hasConfirmedState() const
{
if (m_list_of_events.empty()) return false;
const RewindInfo *ri = m_list_of_events[0];
return ri->isState() && ri->isConfirmed();
} // hasConfirmedState
}; // TimeStepInfo
#endif