Refactored the queue handling to be a separate object from the
RewindManager. Added unit tests.
This commit is contained in:
parent
33caf5ed4b
commit
ef1f48da50
@ -210,6 +210,7 @@
|
||||
#include "network/network_config.hpp"
|
||||
#include "network/network_string.hpp"
|
||||
#include "network/rewind_manager.hpp"
|
||||
#include "network/rewind_queue.hpp"
|
||||
#include "network/servers_manager.hpp"
|
||||
#include "network/stk_host.hpp"
|
||||
#include "network/protocols/get_public_address.hpp"
|
||||
@ -1976,6 +1977,9 @@ void runUnitTests()
|
||||
Log::info("UnitTest", "Fonts for translation");
|
||||
font_manager->unitTesting();
|
||||
|
||||
Log::info("UnitTest", "RewindQueue");
|
||||
RewindQueue::unitTesting();
|
||||
|
||||
Log::info("UnitTest", "=====================");
|
||||
Log::info("UnitTest", "Testing successful ");
|
||||
Log::info("UnitTest", "=====================");
|
||||
|
@ -41,6 +41,8 @@ RewindInfoState::RewindInfoState(float time, Rewinder *rewinder,
|
||||
BareNetworkString *buffer, bool is_confirmed)
|
||||
: RewindInfoRewinder(time, rewinder, buffer, is_confirmed)
|
||||
{
|
||||
// rewinder = NULL is used in unit testing, in which case no world exists
|
||||
if(rewinder!=NULL)
|
||||
m_local_physics_time = Physics::getInstance()->getPhysicsWorld()
|
||||
->getLocalTime();
|
||||
} // RewindInfoState
|
||||
|
@ -159,6 +159,7 @@ public:
|
||||
* It calls undoState in the rewinder. */
|
||||
virtual void undo()
|
||||
{
|
||||
if(m_rewinder) // Unit testing uses NULL as rewinder
|
||||
m_rewinder->undoState(getBuffer());
|
||||
} // undo
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -65,14 +65,6 @@ RewindManager::RewindManager()
|
||||
*/
|
||||
RewindManager::~RewindManager()
|
||||
{
|
||||
// Destroying the
|
||||
AllRewindInfo::const_iterator i;
|
||||
|
||||
for(i=m_rewind_info.begin(); i!=m_rewind_info.end(); ++i)
|
||||
{
|
||||
delete *i;
|
||||
}
|
||||
m_rewind_info.clear();
|
||||
} // ~RewindManager
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -80,10 +72,6 @@ RewindManager::~RewindManager()
|
||||
*/
|
||||
void RewindManager::reset()
|
||||
{
|
||||
#ifdef REWIND_SEARCH_STATS
|
||||
m_count_of_comparisons = 0;
|
||||
m_count_of_searches = 0;
|
||||
#endif
|
||||
m_is_rewinding = false;
|
||||
m_overall_state_size = 0;
|
||||
m_state_frequency = 0.1f; // save 10 states a second
|
||||
@ -104,111 +92,9 @@ void RewindManager::reset()
|
||||
delete rewinder;
|
||||
}
|
||||
|
||||
AllRewindInfo::const_iterator i;
|
||||
for(i=m_rewind_info.begin(); i!=m_rewind_info.end(); i++)
|
||||
{
|
||||
delete *i;
|
||||
}
|
||||
m_rewind_info.clear();
|
||||
m_next_event = m_rewind_info.begin();
|
||||
|
||||
m_network_events.lock();
|
||||
const AllRewindInfo &info = m_network_events.getData();
|
||||
for (i = info.begin(); i != info.end(); ++i)
|
||||
{
|
||||
delete *i;
|
||||
}
|
||||
m_network_events.getData().clear();
|
||||
m_network_events.unlock();
|
||||
m_rewind_queue.reset();
|
||||
} // reset
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** 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 RewindManager::_RewindInfoCompare::operator()(const RewindInfo *ri1,
|
||||
const RewindInfo *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 and
|
||||
// time events are 'smallest', i.e. sorted first (which is necessary for
|
||||
// replay). Time and State events at the same time do not happen, so no
|
||||
// further test are necessary
|
||||
return ri1->isState() || ri1->isTime();
|
||||
} // RewindInfoCompare::operator()
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Inserts a RewindInfo object in the list of all events at the correct time.
|
||||
* If there are several RewindInfo at the exact same time, state RewindInfo
|
||||
* will be insert at the front, and event and time info at the end of the
|
||||
* RewindInfo with the same time.
|
||||
* \param ri The RewindInfo object to insert.
|
||||
*/
|
||||
void RewindManager::insertRewindInfo(RewindInfo *ri)
|
||||
{
|
||||
AllRewindInfo::iterator i = std::upper_bound(m_rewind_info.begin(),
|
||||
m_rewind_info.end(), ri,
|
||||
RewindManager::RewindInfoCompare);
|
||||
m_rewind_info.insert(i, ri);
|
||||
} // insertRewindInfo
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Returns the first (i.e. lowest) index i in m_rewind_info which fulfills
|
||||
* time(i) < target_time <= time(i+1) and is a state. This is the state
|
||||
* from which a rewind can start - all states for the karts will be well
|
||||
* defined.
|
||||
* \param time Time for which an index is searched.
|
||||
* \return Index in m_rewind_info after which to add rewind data.
|
||||
*/
|
||||
RewindManager::AllRewindInfo::reverse_iterator
|
||||
RewindManager::findFirstIndex(float target_time)
|
||||
{
|
||||
// For now do a linear search, even though m_rewind_info is sorted
|
||||
// I would expect that most insertions will be towards the (very)
|
||||
// end of the list, since rewinds should be for short periods of time.
|
||||
// Note that after finding an entry in a binary search, you still
|
||||
// have to do a linear search to find the last entry with the same
|
||||
// time in order to minimise the later necessary memory move.
|
||||
|
||||
// Gather some statistics about search for now:
|
||||
#ifdef REWIND_SEARCH_STATS
|
||||
m_count_of_searches++;
|
||||
#endif
|
||||
AllRewindInfo::reverse_iterator index = m_rewind_info.rbegin();
|
||||
AllRewindInfo::reverse_iterator index_last_state = m_rewind_info.rend();
|
||||
while(index!=m_rewind_info.rend())
|
||||
{
|
||||
#ifdef REWIND_SEARCH_STATS
|
||||
m_count_of_comparisons++;
|
||||
#endif
|
||||
if((*index)->isState())
|
||||
{
|
||||
if( (*index)->getTime() <= target_time )
|
||||
{
|
||||
return index;
|
||||
}
|
||||
index_last_state = index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
if(index_last_state==m_rewind_info.rend())
|
||||
{
|
||||
Log::fatal("RewindManager",
|
||||
"Can't find any state when rewinding to %f - aborting.",
|
||||
target_time);
|
||||
}
|
||||
|
||||
// Otherwise use the last found state - not much we can do in this case.
|
||||
Log::error("RewindManager",
|
||||
"Can't find state to rewind to for time %f, using %f.",
|
||||
target_time, (*index_last_state)->getTime());
|
||||
return index_last_state; // avoid compiler warning
|
||||
} // findFirstIndex
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Adds an event to the rewind data. The data to be stored must be allocated
|
||||
* and not freed by the caller!
|
||||
@ -225,13 +111,12 @@ void RewindManager::addEvent(EventRewinder *event_rewinder,
|
||||
Log::error("RewindManager", "Adding event when rewinding");
|
||||
return;
|
||||
}
|
||||
|
||||
Log::verbose("RewindManager", "Time world %f self-event %f",
|
||||
World::getWorld()->getTime(), getCurrentTime());
|
||||
if (time < 0)
|
||||
time = getCurrentTime();
|
||||
RewindInfo *ri = new RewindInfoEvent(time, event_rewinder,
|
||||
buffer, confirmed);
|
||||
insertRewindInfo(ri);
|
||||
m_rewind_queue.addEvent(event_rewinder, buffer, confirmed, time);
|
||||
} // addEvent
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -245,21 +130,9 @@ void RewindManager::addEvent(EventRewinder *event_rewinder,
|
||||
void RewindManager::addNetworkEvent(EventRewinder *event_rewinder,
|
||||
BareNetworkString *buffer, float time)
|
||||
{
|
||||
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,
|
||||
RewindManager::RewindInfoCompare );
|
||||
m_network_events.getData().insert(i, ri);
|
||||
m_network_events.unlock();
|
||||
m_rewind_queue.addNetworkEvent(event_rewinder, buffer, time);
|
||||
Log::verbose("RewindManager", "Time world %f network-event %f",
|
||||
World::getWorld()->getTime(), time);
|
||||
|
||||
} // addEvent
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -273,18 +146,8 @@ void RewindManager::addNetworkEvent(EventRewinder *event_rewinder,
|
||||
void RewindManager::addNetworkState(int rewinder_index,
|
||||
BareNetworkString *buffer, float time)
|
||||
{
|
||||
RewindInfo *ri = new RewindInfoState(time, m_all_rewinder[rewinder_index],
|
||||
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,
|
||||
RewindManager::RewindInfoCompare );
|
||||
m_network_events.getData().insert(i, ri);
|
||||
m_network_events.unlock();
|
||||
m_rewind_queue.addNetworkState(m_all_rewinder[rewinder_index], buffer,
|
||||
time);
|
||||
Log::verbose("RewindManager", "Time world %f network-state %f",
|
||||
World::getWorld()->getTime(), time);
|
||||
} // addState
|
||||
@ -311,14 +174,11 @@ void RewindManager::saveStates()
|
||||
{
|
||||
// No full state necessary, add a dummy entry for the time
|
||||
// which increases replay precision (same time step size)
|
||||
RewindInfo *ri = new RewindInfoTime(getCurrentTime());
|
||||
insertRewindInfo(ri);
|
||||
m_rewind_queue.addTimeEvent(getCurrentTime());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool was_empty = m_rewind_info.empty();
|
||||
|
||||
// For now always create a snapshot.
|
||||
if (NetworkConfig::get()->isServer())
|
||||
GameProtocol::getInstance()->startNewState();
|
||||
@ -329,11 +189,8 @@ void RewindManager::saveStates()
|
||||
if(buffer && buffer->size()>=0)
|
||||
{
|
||||
m_overall_state_size += buffer->size();
|
||||
RewindInfo *ri = new RewindInfoState(getCurrentTime(),
|
||||
*i, buffer,
|
||||
/*is_confirmed*/true);
|
||||
assert(ri);
|
||||
insertRewindInfo(ri);
|
||||
m_rewind_queue.addState(*i, buffer, /*confirmed*/true,
|
||||
getCurrentTime());
|
||||
if(NetworkConfig::get()->isServer())
|
||||
GameProtocol::getInstance()->addState(buffer);
|
||||
} // size >= 0
|
||||
@ -343,12 +200,8 @@ void RewindManager::saveStates()
|
||||
if (NetworkConfig::get()->isServer())
|
||||
GameProtocol::getInstance()->sendState();
|
||||
|
||||
if (was_empty && !m_rewind_info.empty())
|
||||
m_next_event = m_rewind_info.begin();
|
||||
Log::verbose("RewindManager", "%f allocated %ld bytes search %d/%d=%f",
|
||||
World::getWorld()->getTime(), m_overall_state_size,
|
||||
m_count_of_comparisons, m_count_of_searches,
|
||||
float(m_count_of_comparisons)/ float(m_count_of_searches) );
|
||||
Log::verbose("RewindManager", "%f allocated %ld",
|
||||
World::getWorld()->getTime(), m_overall_state_size);
|
||||
|
||||
m_last_saved_state = time;
|
||||
} // saveStates
|
||||
@ -359,68 +212,39 @@ void RewindManager::saveStates()
|
||||
*/
|
||||
void RewindManager::playEventsTill(float time)
|
||||
{
|
||||
m_network_events.lock();
|
||||
if (m_rewind_info.empty() && m_network_events.getData().empty())
|
||||
{
|
||||
m_network_events.unlock();
|
||||
// No events, nothing to do
|
||||
if (m_rewind_queue.isEmpty())
|
||||
return;
|
||||
}
|
||||
|
||||
/** First merge all newly received network events into the main
|
||||
* event list */
|
||||
bool rewind_necessary = false;
|
||||
float rewind_time = -1.0f;
|
||||
bool needs_rewind;
|
||||
float rewind_time;
|
||||
|
||||
if (!m_network_events.getData().empty())
|
||||
{
|
||||
// Check if a rewind is necessary
|
||||
rewind_time = m_network_events.getData().front()->getTime();
|
||||
Log::info("RewindManager", "At %f merging states from %f",
|
||||
m_rewind_queue.mergeNetworkData(&needs_rewind, &rewind_time);
|
||||
if(needs_rewind)
|
||||
Log::info("RewindManager", "At %f merging states from %f needs rewind",
|
||||
World::getWorld()->getTime(), rewind_time);
|
||||
if (rewind_time < time)
|
||||
{
|
||||
else
|
||||
Log::info("RewindManager", "At %f no need for rewind",
|
||||
World::getWorld()->getTime());
|
||||
|
||||
Log::info("RewindManager",
|
||||
"Rewind necessary at %f because of event at %f",
|
||||
time, rewind_time);
|
||||
rewind_necessary = true;
|
||||
}
|
||||
// It is possible that m_next_event 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_next_eventr (note that it was tested previously that
|
||||
// the list is not empty), merge in the new events, and then
|
||||
// increment m_next_event again. If elements have been added to
|
||||
// the end of the list, m_next_event will now corretly point to them.
|
||||
|
||||
bool adjust_next = !m_rewind_info.empty();
|
||||
if(adjust_next) m_next_event--;
|
||||
m_rewind_info.merge(m_network_events.getData(),
|
||||
RewindManager::RewindInfoCompare);
|
||||
if (adjust_next)
|
||||
m_next_event++;
|
||||
else // rewind_info was empty, set pointer to first element
|
||||
m_next_event = m_rewind_info.begin();
|
||||
} // if !m_network_events empty
|
||||
|
||||
m_network_events.unlock();
|
||||
|
||||
if (rewind_necessary)
|
||||
if (needs_rewind)
|
||||
{
|
||||
rewindTo(rewind_time);
|
||||
}
|
||||
|
||||
assert(!m_is_rewinding);
|
||||
m_is_rewinding = true;
|
||||
while (m_next_event !=m_rewind_info.end() )
|
||||
|
||||
while(m_rewind_queue.hasMoreRewindInfo())
|
||||
{
|
||||
RewindInfo *ri = *m_next_event;
|
||||
RewindInfo *ri = m_rewind_queue.getNext();
|
||||
if (ri->getTime() > time)
|
||||
{
|
||||
m_is_rewinding = false;
|
||||
return;
|
||||
}
|
||||
m_next_event++;
|
||||
++m_rewind_queue;
|
||||
if(ri->isEvent())
|
||||
ri->rewind();
|
||||
}
|
||||
@ -438,37 +262,11 @@ void RewindManager::rewindTo(float rewind_time)
|
||||
StkTime::getRealTime());
|
||||
history->doReplayHistory(History::HISTORY_NONE);
|
||||
|
||||
// First find the state to which we need to rewind
|
||||
// ------------------------------------------------
|
||||
AllRewindInfo::reverse_iterator rindex = findFirstIndex(rewind_time);
|
||||
//rindex--;
|
||||
AllRewindInfo::reverse_iterator xx = rindex;
|
||||
++xx;
|
||||
AllRewindInfo::iterator index = --(xx.base());
|
||||
|
||||
if(!(*rindex)->isState())
|
||||
{
|
||||
Log::error("RewindManager", "No state for rewind to %f, state %d.",
|
||||
rewind_time, index);
|
||||
return;
|
||||
}
|
||||
m_is_rewinding = true;
|
||||
|
||||
// Then undo the rewind infos going backwards in time
|
||||
// --------------------------------------------------
|
||||
AllRewindInfo::reverse_iterator i;
|
||||
for(i= m_rewind_info.rbegin(); i!=rindex; i++)
|
||||
{
|
||||
(*i)->undo();
|
||||
|
||||
// Now all states after the time we rewind to are not confirmed
|
||||
// anymore. They need to be rewritten when going forward during
|
||||
// the rewind.
|
||||
if((*i)->isState() &&
|
||||
(*i)->getTime() > (*index)->getTime() )
|
||||
(*i)->setConfirmed(false);
|
||||
} // for i>state
|
||||
|
||||
m_is_rewinding = true;
|
||||
m_rewind_queue.undoUntil(rewind_time);
|
||||
|
||||
// Rewind the required state(s)
|
||||
// ----------------------------
|
||||
@ -477,7 +275,7 @@ void RewindManager::rewindTo(float rewind_time)
|
||||
|
||||
// Get the (first) full state to which we have to rewind
|
||||
RewindInfoState *state =
|
||||
dynamic_cast<RewindInfoState*>(*rindex);
|
||||
dynamic_cast<RewindInfoState*>(m_rewind_queue.getNext());
|
||||
|
||||
// Store the time to which we have to replay to
|
||||
float exact_rewind_time = state->getTime();
|
||||
@ -493,9 +291,9 @@ void RewindManager::rewindTo(float rewind_time)
|
||||
while(state && state->getTime()==exact_rewind_time)
|
||||
{
|
||||
state->rewind();
|
||||
index++;
|
||||
if(index==m_rewind_info.end()) break;
|
||||
state = dynamic_cast<RewindInfoState*>(*index);
|
||||
++m_rewind_queue;
|
||||
if(!m_rewind_queue.hasMoreRewindInfo()) break;
|
||||
state = dynamic_cast<RewindInfoState*>(m_rewind_queue.getNext());
|
||||
}
|
||||
|
||||
// Now go forward through the list of rewind infos:
|
||||
@ -507,22 +305,27 @@ void RewindManager::rewindTo(float rewind_time)
|
||||
{
|
||||
// Now handle all states and events at the current time before
|
||||
// updating the world:
|
||||
while(index !=m_rewind_info.end() &&
|
||||
(*index)->getTime()<=world->getTime())
|
||||
if (m_rewind_queue.hasMoreRewindInfo())
|
||||
{
|
||||
if((*index)->isState())
|
||||
RewindInfo *ri = m_rewind_queue.getNext();
|
||||
while (ri->getTime() <= world->getTime())
|
||||
{
|
||||
if (ri->isState())
|
||||
{
|
||||
// TOOD: replace the old state with a new state.
|
||||
// For now just set it to confirmed
|
||||
(*index)->setConfirmed(true);
|
||||
ri->setConfirmed(true);
|
||||
}
|
||||
else if((*index)->isEvent())
|
||||
else if (ri->isEvent())
|
||||
{
|
||||
(*index)->rewind();
|
||||
ri->rewind();
|
||||
}
|
||||
index++;
|
||||
}
|
||||
float dt = determineTimeStepSize(index, current_time);
|
||||
|
||||
++m_rewind_queue;
|
||||
} // while ri->getTime() <= world->getTime()
|
||||
} // if m_rewind_queue.hasMoreRewindInfo()
|
||||
|
||||
float dt = m_rewind_queue.determineNextDT(current_time);
|
||||
world->updateWorld(dt);
|
||||
#undef SHOW_ROLLBACK
|
||||
#ifdef SHOW_ROLLBACK
|
||||
@ -538,25 +341,3 @@ void RewindManager::rewindTo(float rewind_time)
|
||||
} // rewindTo
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Determines the next time step size to use when recomputing the physics.
|
||||
* The time step size is either 1/60 (default physics), or less, if there
|
||||
* is an even to handle before that time.
|
||||
* \param next_state The next state to replay.
|
||||
* \param end_time The end time to which we must replay forward. Don't
|
||||
* return a dt that would be bigger tham this value.
|
||||
* \return The time step size to use in the next simulation step.
|
||||
*/
|
||||
float RewindManager::determineTimeStepSize(AllRewindInfo::iterator next_state,
|
||||
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(next_state !=m_rewind_info.end())
|
||||
return (*next_state)->getTime() - World::getWorld()->getTime();
|
||||
|
||||
// Otherwise, i.e. we are rewinding the last state/event, take the
|
||||
// difference between that time and the world time at which the rewind
|
||||
// was triggered.
|
||||
return end_time - (*(--next_state))->getTime();
|
||||
|
||||
} // determineTimeStepSize
|
||||
|
@ -20,6 +20,7 @@
|
||||
#define HEADER_REWIND_MANAGER_HPP
|
||||
|
||||
#include "network/rewinder.hpp"
|
||||
#include "network/rewind_queue.hpp"
|
||||
#include "utils/ptr_vector.hpp"
|
||||
#include "utils/synchronised.hpp"
|
||||
|
||||
@ -90,21 +91,8 @@ private:
|
||||
/** A list of all objects that can be rewound. */
|
||||
AllRewinder m_all_rewinder;
|
||||
|
||||
/** Pointer to all saved states. */
|
||||
typedef std::list<RewindInfo*> AllRewindInfo;
|
||||
|
||||
/** The list of all events that are affected by a rewind. */
|
||||
AllRewindInfo m_rewind_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;
|
||||
|
||||
/** Index of the next event to be used when playing events. */
|
||||
AllRewindInfo::const_iterator m_next_event;
|
||||
/** The queue that stores all rewind infos. */
|
||||
RewindQueue m_rewind_queue;
|
||||
|
||||
/** Overall amount of memory allocated by states. */
|
||||
unsigned int m_overall_state_size;
|
||||
@ -124,25 +112,8 @@ private:
|
||||
* events later. */
|
||||
float m_current_time;
|
||||
|
||||
#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
|
||||
|
||||
RewindManager();
|
||||
~RewindManager();
|
||||
AllRewindInfo::reverse_iterator findFirstIndex(float time);
|
||||
void insertRewindInfo(RewindInfo *ri);
|
||||
float determineTimeStepSize(AllRewindInfo::iterator state, float max_time);
|
||||
// ------------------------------------------------------------------------
|
||||
struct _RewindInfoCompare
|
||||
{
|
||||
bool operator()(const RewindInfo *ri1, const RewindInfo *r2) const;
|
||||
} RewindInfoCompare; // _RewindInfoCompare
|
||||
|
||||
public:
|
||||
// First static functions to manage rewinding.
|
||||
|
555
src/network/rewind_queue.cpp
Executable file
555
src/network/rewind_queue.cpp
Executable file
@ -0,0 +1,555 @@
|
||||
//
|
||||
// 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/rewind_queue.hpp"
|
||||
|
||||
#include "modes/world.hpp"
|
||||
#include "network/rewind_info.hpp"
|
||||
#include "network/rewind_manager.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
/** The constructor.
|
||||
*/
|
||||
RewindQueue::RewindQueue()
|
||||
{
|
||||
reset();
|
||||
} // RewindQueue
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Frees all saved state information. Note that the Rewinder data must be
|
||||
* freed elsewhere.
|
||||
*/
|
||||
RewindQueue::~RewindQueue()
|
||||
{
|
||||
// Destroying the
|
||||
AllRewindInfo::const_iterator i;
|
||||
|
||||
for(i=m_rewind_info.begin(); i!=m_rewind_info.end(); ++i)
|
||||
{
|
||||
delete *i;
|
||||
}
|
||||
m_rewind_info.clear();
|
||||
} // ~RewindQueue
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Frees all saved state information and all destroyable rewinder.
|
||||
*/
|
||||
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++)
|
||||
{
|
||||
delete *i;
|
||||
}
|
||||
m_rewind_info.clear();
|
||||
m_current = m_rewind_info.begin();
|
||||
|
||||
m_network_events.lock();
|
||||
const AllRewindInfo &info = m_network_events.getData();
|
||||
for (i = info.begin(); i != info.end(); ++i)
|
||||
{
|
||||
delete *i;
|
||||
}
|
||||
m_network_events.getData().clear();
|
||||
m_network_events.unlock();
|
||||
} // reset
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** 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
|
||||
{
|
||||
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;
|
||||
|
||||
} // RewindQueue::operator()
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Inserts a RewindInfo object in the list of all events at the correct time.
|
||||
* If there are several RewindInfo at the exact same time, state RewindInfo
|
||||
* will be insert at the front, and event and time info at the end of the
|
||||
* RewindInfo with the same time.
|
||||
* \param ri The RewindInfo object to insert.
|
||||
*/
|
||||
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;
|
||||
} // insertRewindInfo
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** 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 buffer Pointer to the event data.
|
||||
*/
|
||||
void RewindQueue::addEvent(EventRewinder *event_rewinder,
|
||||
BareNetworkString *buffer, bool confirmed,
|
||||
float time )
|
||||
{
|
||||
RewindInfo *ri = new RewindInfoEvent(time, event_rewinder,
|
||||
buffer, confirmed);
|
||||
insertRewindInfo(ri);
|
||||
} // addEvent
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Adds a state from the local simulation. It is not thread-safe, so needs to
|
||||
* be called from the main thread
|
||||
*/
|
||||
void RewindQueue::addState(Rewinder *rewinder, BareNetworkString *buffer,
|
||||
bool confirmed, float time)
|
||||
{
|
||||
RewindInfo *ri = new RewindInfoState(time, rewinder, buffer, confirmed);
|
||||
assert(ri);
|
||||
insertRewindInfo(ri);
|
||||
} // addState
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** 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
|
||||
* 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::addNetworkEvent(EventRewinder *event_rewinder,
|
||||
BareNetworkString *buffer, float time)
|
||||
{
|
||||
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.unlock();
|
||||
} // addEvent
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** 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
|
||||
* 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)
|
||||
{
|
||||
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.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
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Merges thread-safe all data received from the network with the current
|
||||
* local rewind information.
|
||||
* \param needs_rewind True if network rewind information was received which
|
||||
* was in the past (of this simulation), so a rewind must be performed.
|
||||
* \param rewind_time If needs_rewind is true, the time to which a rewind must
|
||||
* be performed (at least). Otherwise undefined, but the value might
|
||||
* be modified in this function.
|
||||
*/
|
||||
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())
|
||||
{
|
||||
m_network_events.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
/** First merge all newly received network events into the main
|
||||
* event list */
|
||||
|
||||
*rewind_time = -1.0f;
|
||||
bool adjust_next = false;
|
||||
|
||||
if (!m_network_events.getData().empty())
|
||||
{
|
||||
// Check if a rewind is necessary
|
||||
*rewind_time = m_network_events.getData().front()->getTime();
|
||||
if (*rewind_time < World::getWorld()->getTime())
|
||||
{
|
||||
*needs_rewind = true;
|
||||
}
|
||||
// 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
|
||||
|
||||
m_network_events.unlock();
|
||||
|
||||
|
||||
} // mergeNetworkData
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
bool RewindQueue::isEmpty() const
|
||||
{
|
||||
if (m_current != m_rewind_info.end())
|
||||
return false;
|
||||
|
||||
m_network_events.lock();
|
||||
bool no_network_events = m_network_events.getData().empty();
|
||||
m_network_events.unlock();
|
||||
|
||||
return no_network_events;
|
||||
} // isEmpty
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Returns true if there is at least one more RewindInfo available.
|
||||
*/
|
||||
bool RewindQueue::hasMoreRewindInfo() const
|
||||
{
|
||||
return m_current != m_rewind_info.end();
|
||||
} // hasMoreRewindInfo
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Determines the next time step size to use when recomputing the physics.
|
||||
* The time step size is either 1/60 (default physics), or less, if there
|
||||
* is an even to handle before that time.
|
||||
* \param next_state The next state to replay.
|
||||
* \param end_time The end time to which we must replay forward. Don't
|
||||
* return a dt that would be bigger tham this value.
|
||||
* \return The time step size to use in the next simulation step.
|
||||
*/
|
||||
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())
|
||||
return (*m_current)->getTime() - World::getWorld()->getTime();
|
||||
|
||||
// Otherwise, i.e. we are rewinding the last state/event, take the
|
||||
// difference between that time and the world time at which the rewind
|
||||
// was triggered.
|
||||
return end_time - (*(--m_current))->getTime();
|
||||
|
||||
} // determineNextDT
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** 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.
|
||||
* \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())
|
||||
{
|
||||
--i;
|
||||
if ((*i)->isState() &&
|
||||
(*i)->getTime() <= undo_time)
|
||||
{
|
||||
// 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()
|
||||
|
||||
Log::error("RewindManager", "No state for rewind to %f",
|
||||
undo_time);
|
||||
m_current = m_rewind_info.begin();
|
||||
|
||||
} // undoUntil
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Tests the sorting order of events with the same time stamp: states must
|
||||
* be first, then time info, then events.
|
||||
*/
|
||||
void RewindQueue::testingSortingOrderType(EventRewinder *rewinder, int types[3])
|
||||
{
|
||||
for(unsigned int i=0; i<3; i++)
|
||||
{
|
||||
switch (types[i])
|
||||
{
|
||||
case 1: // Insert State
|
||||
{
|
||||
BareNetworkString *s = new BareNetworkString();
|
||||
s->addUInt8(1);
|
||||
addState(NULL, s, true, 0.0f);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
addTimeEvent(0.0f);
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
BareNetworkString *s = new BareNetworkString();
|
||||
s->addUInt8(2);
|
||||
addEvent(rewinder, s, true, 0.0f);
|
||||
break;
|
||||
}
|
||||
default: assert(false);
|
||||
} // switch
|
||||
} // for i =0; i<3
|
||||
|
||||
// This should go back to the first state
|
||||
undoUntil(0.0);
|
||||
|
||||
// Now test if the three events are sorted in the right order:
|
||||
// State, then time, then event
|
||||
assert(hasMoreRewindInfo());
|
||||
assert(!isEmpty());
|
||||
RewindInfo *ri = getNext();
|
||||
assert(ri->getTime() == 0.0f);
|
||||
assert(ri->isState());
|
||||
RewindInfoState *ris = dynamic_cast<RewindInfoState*>(ri);
|
||||
assert(ris->getBuffer()->getTotalSize() == 1);
|
||||
assert(ris->getBuffer()->getUInt8() == 1);
|
||||
|
||||
operator++();
|
||||
ri = getNext();
|
||||
assert(!isEmpty());
|
||||
assert(hasMoreRewindInfo());
|
||||
assert(ri->getTime() == 0.0f);
|
||||
assert(ri->isTime());
|
||||
|
||||
operator++();
|
||||
ri = getNext();
|
||||
assert(!isEmpty());
|
||||
assert(hasMoreRewindInfo());
|
||||
assert(ri->getTime() == 0.0f);
|
||||
assert(ri->isEvent());
|
||||
RewindInfoEvent *rie = dynamic_cast<RewindInfoEvent*>(ri);
|
||||
assert(rie->getBuffer()->getTotalSize() == 1);
|
||||
assert(rie->getBuffer()->getUInt8() == 2);
|
||||
|
||||
operator++();
|
||||
assert(isEmpty());
|
||||
assert(!hasMoreRewindInfo());
|
||||
|
||||
} // testingSortingOrderType
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Tests sorting of rewind infos according to their time. It assumes
|
||||
* different times for all events.
|
||||
* \param types The types for the three events.
|
||||
* \param times The times at which the events happened. Must be >0
|
||||
* (since an additional state at t=0 is added to avoid
|
||||
* warnings during undoUntil() ).
|
||||
*/
|
||||
void RewindQueue::testingSortingOrderTime(EventRewinder *rewinder,
|
||||
int types[3], float times[3])
|
||||
{
|
||||
float min_rewind_time = std::min({ times[0], times[1], times[2] });
|
||||
|
||||
// Avoid warnings about 'no state found' when rewinding
|
||||
// and there is indeed no state at the given time.
|
||||
addState(NULL, new BareNetworkString(), true, 0);
|
||||
for (unsigned int i = 0; i<3; i++)
|
||||
{
|
||||
switch (types[i])
|
||||
{
|
||||
case 1: // Insert State
|
||||
{
|
||||
BareNetworkString *s = new BareNetworkString();
|
||||
s->addUInt8(1);
|
||||
addState(NULL, s, true, times[i]);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
addTimeEvent(times[i]);
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
BareNetworkString *s = new BareNetworkString();
|
||||
s->addUInt8(2);
|
||||
addEvent(rewinder, s, true, times[i]);
|
||||
break;
|
||||
}
|
||||
default: assert(false);
|
||||
} // switch
|
||||
|
||||
} // for i =0; i<3
|
||||
|
||||
// This should go back to the first state
|
||||
undoUntil(min_rewind_time);
|
||||
|
||||
RewindInfo *ri_prev = getNext();
|
||||
operator++();
|
||||
while (hasMoreRewindInfo())
|
||||
{
|
||||
RewindInfo *ri = getNext();
|
||||
assert(ri_prev->getTime() < ri->getTime());
|
||||
ri_prev = ri;
|
||||
operator++();
|
||||
} // while hasMoreRewindInfo
|
||||
|
||||
} // testingSortingOrderTime
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Unit tests for RewindQueue. It tests:
|
||||
* - Sorting order of RewindInfos at the same time (i.e. state before time
|
||||
* before events).
|
||||
* - Sorting order of RewindInfos with different timestamps (and a mixture
|
||||
* of types).
|
||||
* - Special cases that triggered incorrect behaviour previously.
|
||||
*/
|
||||
void RewindQueue::unitTesting()
|
||||
{
|
||||
// Some classes need the RewindManager (to register themselves with)
|
||||
RewindManager::create();
|
||||
|
||||
// A dummy Rewinder and EventRewinder class since some of the calls being
|
||||
// tested here need an instance.
|
||||
class DummyRewinder : public Rewinder, public EventRewinder
|
||||
{
|
||||
public:
|
||||
BareNetworkString* saveState() const { return NULL; }
|
||||
virtual void undoEvent(BareNetworkString *s) {}
|
||||
virtual void rewindToEvent(BareNetworkString *s) {}
|
||||
virtual void rewindToState(BareNetworkString *s) {}
|
||||
virtual void undoState(BareNetworkString *s) {}
|
||||
virtual void undo(BareNetworkString *s) {}
|
||||
virtual void rewind(BareNetworkString *s) {}
|
||||
DummyRewinder() : Rewinder(true) {}
|
||||
};
|
||||
DummyRewinder *dummy_rewinder = new DummyRewinder();
|
||||
|
||||
RewindQueue q0;
|
||||
assert(q0.isEmpty());
|
||||
|
||||
// Test sorting of different RewindInfo with identical time stamps.
|
||||
// ----------------------------------------------------------------
|
||||
int types[6][3] = { { 1,2,3 }, { 1,3,2 }, { 2,1,3 },
|
||||
{ 2,3,1 }, { 3,1,2 }, { 3,2,1 } };
|
||||
float times[6][3] = { { 1,2,3 }, { 1,3,2 }, { 2,1,3 },
|
||||
{ 2,3,1 }, { 3,1,2 }, { 3,2,1 } };
|
||||
for (unsigned int i = 0; i < 6; i++)
|
||||
{
|
||||
RewindQueue *rq = new RewindQueue();
|
||||
rq->testingSortingOrderType(dummy_rewinder, types[i]);
|
||||
delete rq;
|
||||
}
|
||||
|
||||
// Test sorting of different RewindInfo at different times
|
||||
// Checks all combinations of times and types.
|
||||
// -------------------------------------------------------
|
||||
for (unsigned int i = 0; i < 6; i++)
|
||||
{
|
||||
for (unsigned int j = 0; j < 6; j++)
|
||||
{
|
||||
RewindQueue *rq = new RewindQueue();
|
||||
rq->testingSortingOrderTime(dummy_rewinder, types[i], times[j]);
|
||||
delete rq;
|
||||
} // for j in times
|
||||
} // for i in types
|
||||
|
||||
// Bugs seen before
|
||||
// ----------------
|
||||
// 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; // Should now point at end of list
|
||||
b1.hasMoreRewindInfo();
|
||||
b1.addEvent(NULL, new BareNetworkString(), true, 2.0f);
|
||||
RewindInfo *ri = b1.getNext();
|
||||
assert(ri->getTime() == 2.0f);
|
||||
assert(ri->isEvent());
|
||||
|
||||
} // unitTesting
|
118
src/network/rewind_queue.hpp
Executable file
118
src/network/rewind_queue.hpp
Executable file
@ -0,0 +1,118 @@
|
||||
//
|
||||
// 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_REWIND_QUEUE_HPP
|
||||
#define HEADER_REWIND_QUEUE_HPP
|
||||
|
||||
#include "network/rewinder.hpp"
|
||||
#include "utils/ptr_vector.hpp"
|
||||
#include "utils/synchronised.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
class RewindInfo;
|
||||
class EventRewinder;
|
||||
|
||||
/** \ingroup network
|
||||
*/
|
||||
|
||||
class RewindQueue
|
||||
{
|
||||
private:
|
||||
|
||||
/** Pointer to all saved states. */
|
||||
typedef std::list<RewindInfo*> AllRewindInfo;
|
||||
|
||||
/** The list of all events that are affected by a rewind. */
|
||||
AllRewindInfo m_rewind_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;
|
||||
|
||||
/** 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
|
||||
|
||||
void insertRewindInfo(RewindInfo *ri);
|
||||
|
||||
struct _RewindInfoCompare
|
||||
{
|
||||
bool operator()(const RewindInfo *ri1, const RewindInfo *ri2) const;
|
||||
} m_rewind_info_compare;
|
||||
|
||||
void testingSortingOrderType(EventRewinder *rewinder, int types[3]);
|
||||
void testingSortingOrderTime(EventRewinder *rewinder, int types[3],
|
||||
float times[3] );
|
||||
|
||||
public:
|
||||
static void unitTesting();
|
||||
|
||||
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 addNetworkEvent(EventRewinder *event_rewinder,
|
||||
BareNetworkString *buffer, float time);
|
||||
void addNetworkState(Rewinder *rewinder, BareNetworkString *buffer,
|
||||
float time);
|
||||
void addTimeEvent(float time);
|
||||
void mergeNetworkData(bool *needs_rewind, float *rewind_time);
|
||||
bool isEmpty() const;
|
||||
bool hasMoreRewindInfo() const;
|
||||
void undoUntil(float undo_time);
|
||||
float determineNextDT(float max_time);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
RewindQueue::AllRewindInfo::iterator& operator++()
|
||||
{
|
||||
assert(m_current != m_rewind_info.end());
|
||||
m_current++;
|
||||
return m_current;
|
||||
} // operator++
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
/** Returns the next event. Caller must make sure that there is at least
|
||||
* one more RewindInfo (see hasMoreRewindInfo()). */
|
||||
RewindInfo *getNext()
|
||||
{
|
||||
assert(m_current != m_rewind_info.end());
|
||||
return *m_current;
|
||||
} // getNext
|
||||
|
||||
}; // RewindQueue
|
||||
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user