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_config.hpp"
|
||||||
#include "network/network_string.hpp"
|
#include "network/network_string.hpp"
|
||||||
#include "network/rewind_manager.hpp"
|
#include "network/rewind_manager.hpp"
|
||||||
|
#include "network/rewind_queue.hpp"
|
||||||
#include "network/servers_manager.hpp"
|
#include "network/servers_manager.hpp"
|
||||||
#include "network/stk_host.hpp"
|
#include "network/stk_host.hpp"
|
||||||
#include "network/protocols/get_public_address.hpp"
|
#include "network/protocols/get_public_address.hpp"
|
||||||
@ -1976,6 +1977,9 @@ void runUnitTests()
|
|||||||
Log::info("UnitTest", "Fonts for translation");
|
Log::info("UnitTest", "Fonts for translation");
|
||||||
font_manager->unitTesting();
|
font_manager->unitTesting();
|
||||||
|
|
||||||
|
Log::info("UnitTest", "RewindQueue");
|
||||||
|
RewindQueue::unitTesting();
|
||||||
|
|
||||||
Log::info("UnitTest", "=====================");
|
Log::info("UnitTest", "=====================");
|
||||||
Log::info("UnitTest", "Testing successful ");
|
Log::info("UnitTest", "Testing successful ");
|
||||||
Log::info("UnitTest", "=====================");
|
Log::info("UnitTest", "=====================");
|
||||||
|
@ -41,6 +41,8 @@ RewindInfoState::RewindInfoState(float time, Rewinder *rewinder,
|
|||||||
BareNetworkString *buffer, bool is_confirmed)
|
BareNetworkString *buffer, bool is_confirmed)
|
||||||
: RewindInfoRewinder(time, rewinder, buffer, 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()
|
m_local_physics_time = Physics::getInstance()->getPhysicsWorld()
|
||||||
->getLocalTime();
|
->getLocalTime();
|
||||||
} // RewindInfoState
|
} // RewindInfoState
|
||||||
|
@ -159,6 +159,7 @@ public:
|
|||||||
* It calls undoState in the rewinder. */
|
* It calls undoState in the rewinder. */
|
||||||
virtual void undo()
|
virtual void undo()
|
||||||
{
|
{
|
||||||
|
if(m_rewinder) // Unit testing uses NULL as rewinder
|
||||||
m_rewinder->undoState(getBuffer());
|
m_rewinder->undoState(getBuffer());
|
||||||
} // undo
|
} // undo
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -65,14 +65,6 @@ RewindManager::RewindManager()
|
|||||||
*/
|
*/
|
||||||
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
|
} // ~RewindManager
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -80,10 +72,6 @@ RewindManager::~RewindManager()
|
|||||||
*/
|
*/
|
||||||
void RewindManager::reset()
|
void RewindManager::reset()
|
||||||
{
|
{
|
||||||
#ifdef REWIND_SEARCH_STATS
|
|
||||||
m_count_of_comparisons = 0;
|
|
||||||
m_count_of_searches = 0;
|
|
||||||
#endif
|
|
||||||
m_is_rewinding = false;
|
m_is_rewinding = false;
|
||||||
m_overall_state_size = 0;
|
m_overall_state_size = 0;
|
||||||
m_state_frequency = 0.1f; // save 10 states a second
|
m_state_frequency = 0.1f; // save 10 states a second
|
||||||
@ -104,111 +92,9 @@ void RewindManager::reset()
|
|||||||
delete rewinder;
|
delete rewinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
AllRewindInfo::const_iterator i;
|
m_rewind_queue.reset();
|
||||||
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();
|
|
||||||
} // 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
|
/** Adds an event to the rewind data. The data to be stored must be allocated
|
||||||
* and not freed by the caller!
|
* and not freed by the caller!
|
||||||
@ -225,13 +111,12 @@ void RewindManager::addEvent(EventRewinder *event_rewinder,
|
|||||||
Log::error("RewindManager", "Adding event when rewinding");
|
Log::error("RewindManager", "Adding event when rewinding");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::verbose("RewindManager", "Time world %f self-event %f",
|
Log::verbose("RewindManager", "Time world %f self-event %f",
|
||||||
World::getWorld()->getTime(), getCurrentTime());
|
World::getWorld()->getTime(), getCurrentTime());
|
||||||
if (time < 0)
|
if (time < 0)
|
||||||
time = getCurrentTime();
|
time = getCurrentTime();
|
||||||
RewindInfo *ri = new RewindInfoEvent(time, event_rewinder,
|
m_rewind_queue.addEvent(event_rewinder, buffer, confirmed, time);
|
||||||
buffer, confirmed);
|
|
||||||
insertRewindInfo(ri);
|
|
||||||
} // addEvent
|
} // addEvent
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -245,21 +130,9 @@ void RewindManager::addEvent(EventRewinder *event_rewinder,
|
|||||||
void RewindManager::addNetworkEvent(EventRewinder *event_rewinder,
|
void RewindManager::addNetworkEvent(EventRewinder *event_rewinder,
|
||||||
BareNetworkString *buffer, float time)
|
BareNetworkString *buffer, float time)
|
||||||
{
|
{
|
||||||
RewindInfo *ri = new RewindInfoEvent(time, event_rewinder,
|
m_rewind_queue.addNetworkEvent(event_rewinder, buffer, time);
|
||||||
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();
|
|
||||||
Log::verbose("RewindManager", "Time world %f network-event %f",
|
Log::verbose("RewindManager", "Time world %f network-event %f",
|
||||||
World::getWorld()->getTime(), time);
|
World::getWorld()->getTime(), time);
|
||||||
|
|
||||||
} // addEvent
|
} // addEvent
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -273,18 +146,8 @@ void RewindManager::addNetworkEvent(EventRewinder *event_rewinder,
|
|||||||
void RewindManager::addNetworkState(int rewinder_index,
|
void RewindManager::addNetworkState(int rewinder_index,
|
||||||
BareNetworkString *buffer, float time)
|
BareNetworkString *buffer, float time)
|
||||||
{
|
{
|
||||||
RewindInfo *ri = new RewindInfoState(time, m_all_rewinder[rewinder_index],
|
m_rewind_queue.addNetworkState(m_all_rewinder[rewinder_index], buffer,
|
||||||
buffer, /*confirmed*/true);
|
time);
|
||||||
|
|
||||||
// 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();
|
|
||||||
Log::verbose("RewindManager", "Time world %f network-state %f",
|
Log::verbose("RewindManager", "Time world %f network-state %f",
|
||||||
World::getWorld()->getTime(), time);
|
World::getWorld()->getTime(), time);
|
||||||
} // addState
|
} // addState
|
||||||
@ -311,14 +174,11 @@ void RewindManager::saveStates()
|
|||||||
{
|
{
|
||||||
// No full state necessary, add a dummy entry for the time
|
// No full state necessary, add a dummy entry for the time
|
||||||
// which increases replay precision (same time step size)
|
// which increases replay precision (same time step size)
|
||||||
RewindInfo *ri = new RewindInfoTime(getCurrentTime());
|
m_rewind_queue.addTimeEvent(getCurrentTime());
|
||||||
insertRewindInfo(ri);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool was_empty = m_rewind_info.empty();
|
|
||||||
|
|
||||||
// For now always create a snapshot.
|
// For now always create a snapshot.
|
||||||
if (NetworkConfig::get()->isServer())
|
if (NetworkConfig::get()->isServer())
|
||||||
GameProtocol::getInstance()->startNewState();
|
GameProtocol::getInstance()->startNewState();
|
||||||
@ -329,11 +189,8 @@ void RewindManager::saveStates()
|
|||||||
if(buffer && buffer->size()>=0)
|
if(buffer && buffer->size()>=0)
|
||||||
{
|
{
|
||||||
m_overall_state_size += buffer->size();
|
m_overall_state_size += buffer->size();
|
||||||
RewindInfo *ri = new RewindInfoState(getCurrentTime(),
|
m_rewind_queue.addState(*i, buffer, /*confirmed*/true,
|
||||||
*i, buffer,
|
getCurrentTime());
|
||||||
/*is_confirmed*/true);
|
|
||||||
assert(ri);
|
|
||||||
insertRewindInfo(ri);
|
|
||||||
if(NetworkConfig::get()->isServer())
|
if(NetworkConfig::get()->isServer())
|
||||||
GameProtocol::getInstance()->addState(buffer);
|
GameProtocol::getInstance()->addState(buffer);
|
||||||
} // size >= 0
|
} // size >= 0
|
||||||
@ -343,12 +200,8 @@ void RewindManager::saveStates()
|
|||||||
if (NetworkConfig::get()->isServer())
|
if (NetworkConfig::get()->isServer())
|
||||||
GameProtocol::getInstance()->sendState();
|
GameProtocol::getInstance()->sendState();
|
||||||
|
|
||||||
if (was_empty && !m_rewind_info.empty())
|
Log::verbose("RewindManager", "%f allocated %ld",
|
||||||
m_next_event = m_rewind_info.begin();
|
World::getWorld()->getTime(), m_overall_state_size);
|
||||||
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) );
|
|
||||||
|
|
||||||
m_last_saved_state = time;
|
m_last_saved_state = time;
|
||||||
} // saveStates
|
} // saveStates
|
||||||
@ -359,68 +212,39 @@ void RewindManager::saveStates()
|
|||||||
*/
|
*/
|
||||||
void RewindManager::playEventsTill(float time)
|
void RewindManager::playEventsTill(float time)
|
||||||
{
|
{
|
||||||
m_network_events.lock();
|
// No events, nothing to do
|
||||||
if (m_rewind_info.empty() && m_network_events.getData().empty())
|
if (m_rewind_queue.isEmpty())
|
||||||
{
|
|
||||||
m_network_events.unlock();
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
/** First merge all newly received network events into the main
|
bool needs_rewind;
|
||||||
* event list */
|
float rewind_time;
|
||||||
bool rewind_necessary = false;
|
|
||||||
float rewind_time = -1.0f;
|
|
||||||
|
|
||||||
if (!m_network_events.getData().empty())
|
m_rewind_queue.mergeNetworkData(&needs_rewind, &rewind_time);
|
||||||
{
|
if(needs_rewind)
|
||||||
// Check if a rewind is necessary
|
Log::info("RewindManager", "At %f merging states from %f needs rewind",
|
||||||
rewind_time = m_network_events.getData().front()->getTime();
|
|
||||||
Log::info("RewindManager", "At %f merging states from %f",
|
|
||||||
World::getWorld()->getTime(), rewind_time);
|
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 (needs_rewind)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
rewindTo(rewind_time);
|
rewindTo(rewind_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!m_is_rewinding);
|
assert(!m_is_rewinding);
|
||||||
m_is_rewinding = true;
|
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)
|
if (ri->getTime() > time)
|
||||||
{
|
{
|
||||||
m_is_rewinding = false;
|
m_is_rewinding = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_next_event++;
|
++m_rewind_queue;
|
||||||
if(ri->isEvent())
|
if(ri->isEvent())
|
||||||
ri->rewind();
|
ri->rewind();
|
||||||
}
|
}
|
||||||
@ -438,37 +262,11 @@ void RewindManager::rewindTo(float rewind_time)
|
|||||||
StkTime::getRealTime());
|
StkTime::getRealTime());
|
||||||
history->doReplayHistory(History::HISTORY_NONE);
|
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
|
// Then undo the rewind infos going backwards in time
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
AllRewindInfo::reverse_iterator i;
|
m_is_rewinding = true;
|
||||||
for(i= m_rewind_info.rbegin(); i!=rindex; i++)
|
m_rewind_queue.undoUntil(rewind_time);
|
||||||
{
|
|
||||||
(*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
|
|
||||||
|
|
||||||
|
|
||||||
// Rewind the required state(s)
|
// 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
|
// Get the (first) full state to which we have to rewind
|
||||||
RewindInfoState *state =
|
RewindInfoState *state =
|
||||||
dynamic_cast<RewindInfoState*>(*rindex);
|
dynamic_cast<RewindInfoState*>(m_rewind_queue.getNext());
|
||||||
|
|
||||||
// Store the time to which we have to replay to
|
// Store the time to which we have to replay to
|
||||||
float exact_rewind_time = state->getTime();
|
float exact_rewind_time = state->getTime();
|
||||||
@ -493,9 +291,9 @@ void RewindManager::rewindTo(float rewind_time)
|
|||||||
while(state && state->getTime()==exact_rewind_time)
|
while(state && state->getTime()==exact_rewind_time)
|
||||||
{
|
{
|
||||||
state->rewind();
|
state->rewind();
|
||||||
index++;
|
++m_rewind_queue;
|
||||||
if(index==m_rewind_info.end()) break;
|
if(!m_rewind_queue.hasMoreRewindInfo()) break;
|
||||||
state = dynamic_cast<RewindInfoState*>(*index);
|
state = dynamic_cast<RewindInfoState*>(m_rewind_queue.getNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now go forward through the list of rewind infos:
|
// 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
|
// Now handle all states and events at the current time before
|
||||||
// updating the world:
|
// updating the world:
|
||||||
while(index !=m_rewind_info.end() &&
|
if (m_rewind_queue.hasMoreRewindInfo())
|
||||||
(*index)->getTime()<=world->getTime())
|
|
||||||
{
|
{
|
||||||
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.
|
// TOOD: replace the old state with a new state.
|
||||||
// For now just set it to confirmed
|
// 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++;
|
|
||||||
}
|
++m_rewind_queue;
|
||||||
float dt = determineTimeStepSize(index, current_time);
|
} // while ri->getTime() <= world->getTime()
|
||||||
|
} // if m_rewind_queue.hasMoreRewindInfo()
|
||||||
|
|
||||||
|
float dt = m_rewind_queue.determineNextDT(current_time);
|
||||||
world->updateWorld(dt);
|
world->updateWorld(dt);
|
||||||
#undef SHOW_ROLLBACK
|
#undef SHOW_ROLLBACK
|
||||||
#ifdef SHOW_ROLLBACK
|
#ifdef SHOW_ROLLBACK
|
||||||
@ -538,25 +341,3 @@ void RewindManager::rewindTo(float rewind_time)
|
|||||||
} // rewindTo
|
} // 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
|
#define HEADER_REWIND_MANAGER_HPP
|
||||||
|
|
||||||
#include "network/rewinder.hpp"
|
#include "network/rewinder.hpp"
|
||||||
|
#include "network/rewind_queue.hpp"
|
||||||
#include "utils/ptr_vector.hpp"
|
#include "utils/ptr_vector.hpp"
|
||||||
#include "utils/synchronised.hpp"
|
#include "utils/synchronised.hpp"
|
||||||
|
|
||||||
@ -90,21 +91,8 @@ private:
|
|||||||
/** A list of all objects that can be rewound. */
|
/** A list of all objects that can be rewound. */
|
||||||
AllRewinder m_all_rewinder;
|
AllRewinder m_all_rewinder;
|
||||||
|
|
||||||
/** Pointer to all saved states. */
|
/** The queue that stores all rewind infos. */
|
||||||
typedef std::list<RewindInfo*> AllRewindInfo;
|
RewindQueue m_rewind_queue;
|
||||||
|
|
||||||
/** 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;
|
|
||||||
|
|
||||||
/** Overall amount of memory allocated by states. */
|
/** Overall amount of memory allocated by states. */
|
||||||
unsigned int m_overall_state_size;
|
unsigned int m_overall_state_size;
|
||||||
@ -124,25 +112,8 @@ private:
|
|||||||
* events later. */
|
* events later. */
|
||||||
float m_current_time;
|
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();
|
||||||
~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:
|
public:
|
||||||
// First static functions to manage rewinding.
|
// 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