ixed handling of adding states to time step info to minimise

rewindw by assigning new events to existing time step info events.
This commit is contained in:
hiker 2017-06-05 08:54:52 +10:00
parent 34af963cc1
commit 3747427c5f
10 changed files with 214 additions and 184 deletions

View File

@ -114,8 +114,15 @@
<explosion impulse-objects="500.0" />
<!-- Networking - the current networking code is outdated and will not
work anymore - so for now don't enable this. -->
<networking enable="false"/>
work anymore - so for now don't enable this.
For the new networking code:
combine-threshold: Maximum time a network event can be different
from an existing time step info to be combined into one. This
reduces the number of time steps necessary (for slightly larger
inaccuracies).
-->
<networking enable="false"
combine-threshold="0.05" />
<!-- The field od views for 1-4 player split screen. fov-3 is
actually not used (since 3 player split screen uses the

View File

@ -138,6 +138,8 @@ void STKConfig::load(const std::string &filename)
CHECK_NEG(m_replay_delta_pos2, "replay delta-position" );
CHECK_NEG(m_replay_dt, "replay delta-t" );
CHECK_NEG(m_smooth_angle_limit, "physics smooth-angle-limit" );
CHECK_NEG(m_network_combine_threshold, "network combine-threshold" );
// Square distance to make distance checks cheaper (no sqrt)
m_replay_delta_pos2 *= m_replay_delta_pos2;
@ -170,6 +172,7 @@ void STKConfig::init_defaults()
m_replay_delta_angle = -100;
m_replay_delta_pos2 = -100;
m_replay_dt = -100;
m_network_combine_threshold = -100;
m_title_music = NULL;
m_enable_networking = true;
m_smooth_normals = false;
@ -342,8 +345,11 @@ void STKConfig::getAllData(const XMLNode * root)
ai_node->get("acceleration", &m_ai_acceleration);
}
if(const XMLNode *networking_node= root->getNode("networking"))
if (const XMLNode *networking_node = root->getNode("networking"))
{
networking_node->get("enable", &m_enable_networking);
networking_node->get("combine-threshold", &m_network_combine_threshold);
}
if(const XMLNode *replay_node = root->getNode("replay"))
{

View File

@ -81,6 +81,12 @@ public:
int m_max_karts; /**<Maximum number of karts. */
bool m_smooth_normals; /**< If normals for raycasts for wheels
should be interpolated. */
/** Network events that are less than this value apart will be executed
* at the same time (instead of adding a new time step, which causes
* one more time step to be simulated). */
float m_network_combine_threshold;
/** If the angle between a normal on a vertex and the normal of the
* triangle are more than this value, the physics will use the normal
* of the triangle in smoothing normal. */

View File

@ -33,6 +33,7 @@
#include "network/network_config.hpp"
#include "network/protocol_manager.hpp"
#include "network/race_event_manager.hpp"
#include "network/rewind_manager.hpp"
#include "network/stk_host.hpp"
#include "online/request_manager.hpp"
#include "race/history.hpp"
@ -235,6 +236,14 @@ void MainLoop::run()
m_prev_time = m_curr_time;
float dt = getLimitedDt();
// Add a Time step entry to the rewind list, which can store all
// all input ecents being issued during the driver update.
if (World::getWorld() && RewindManager::get()->isEnabled())
{
RewindManager::get()
->addNextTimeStep(World::getWorld()->getTime(), dt);
}
if (!m_abort && !ProfileWorld::isNoGraphics())
{
// Render the previous frame, and also handle all user input.

View File

@ -97,6 +97,22 @@ void RewindManager::reset()
m_rewind_queue.reset();
} // reset
// ----------------------------------------------------------------------------
/** Adds a new TimeStep entry. Only exception is time=0 (which happens during
* all of 'ready, set, go') - for which only one entry is created.
*/
void RewindManager::addNextTimeStep(float time, float dt)
{
// Add a timestep entry each timestep, except at 'ready, set, go'
// at which time is 0 - we add only one entry there
if (time>0 || m_rewind_queue.isEmpty())
{
Log::info("RewindManager", "Adding new timestamp %f dt %f at %lf",
time, dt, StkTime::getRealTime());
m_rewind_queue.addNewTimeStep(time, dt);
}
} // addNextTimeStep
// ----------------------------------------------------------------------------
/** Adds an event to the rewind data. The data to be stored must be allocated
* and not freed by the caller!
@ -172,12 +188,6 @@ void RewindManager::update(float dt)
float time = World::getWorld()->getTime();
m_not_rewound_time = time;
// Avoid duplicating time step during ready-set-go, all with time 0
if (time > m_last_saved_state)
{
m_rewind_queue.addNewTimeStep(World::getWorld()->getTime(), dt);
}
// Clients don't save state, so they just update m_last_saved_state
// (only for the above if test) and exit.
if ( NetworkConfig::get()->isClient() ||
@ -196,7 +206,8 @@ void RewindManager::update(float dt)
{
m_overall_state_size += buffer->size();
// Add to the previously created container
m_rewind_queue.addLocalState(*rewinder, buffer, /*confirmed*/true);
m_rewind_queue.addLocalState(*rewinder, buffer, /*confirmed*/true,
World::getWorld()->getTime());
GameProtocol::getInstance()->addState(buffer);
} // size >= 0
else
@ -218,14 +229,19 @@ void RewindManager::update(float dt)
*/
void RewindManager::playEventsTill(float time, float *dt)
{
bool needs_rewind;
float rewind_time;
// Merge in all network events that have happened since the last
// merge and that have happened before the current time (which will
// be getTime()+dt - world time has not been updated yet).
m_rewind_queue.mergeNetworkData(World::getWorld()->getTime(), *dt,
&needs_rewind, &rewind_time);
// No events, nothing to do
if (m_rewind_queue.isEmpty())
return;
bool needs_rewind;
float rewind_time;
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);
@ -308,9 +324,6 @@ void RewindManager::rewindTo(float rewind_time)
// if the next event is not a state
float dt = -1.0f;
// Restore the state, then all events for the specified time
current->replayAllStates();
// Need to exit loop if in-game menu is open, since world clock
// will not be increased while the game is paused
if (World::getWorld()->getPhase() == WorldStatus::IN_GAME_MENU_PHASE)

View File

@ -145,6 +145,7 @@ public:
BareNetworkString *buffer, float time);
void addNetworkState(int rewinder_index, BareNetworkString *buffer,
float time);
void addNextTimeStep(float time, float dt);
// ------------------------------------------------------------------------
/** Adds a Rewinder to the list of all rewinders.
* \return true If rewinding is enabled, false otherwise.

View File

@ -18,6 +18,7 @@
#include "network/rewind_queue.hpp"
#include "config/stk_config.hpp"
#include "modes/world.hpp"
#include "network/rewind_info.hpp"
#include "network/rewind_manager.hpp"
@ -29,6 +30,7 @@
*/
RewindQueue::RewindQueue()
{
m_current = m_time_step_info.begin();
reset();
} // RewindQueue
@ -99,7 +101,10 @@ void RewindQueue::addNewTimeStep(float time, float dt)
}
};
m_time_step_info.insert(i, tsi);
// If current was not initialised
AllTimeStepInfo::iterator new_tsi = m_time_step_info.insert(i, tsi);
if (m_current == m_time_step_info.end())
m_current = new_tsi;
} // addNewTimeStep
// ----------------------------------------------------------------------------
@ -109,13 +114,17 @@ void RewindQueue::addNewTimeStep(float time, float dt)
* new TimeStepInfo object).
* \param Time at which the event that needs to be added hapened.
*/
TimeStepInfo *RewindQueue::findClosestTimeStepInfo(float t)
RewindQueue::AllTimeStepInfo::iterator
RewindQueue::findPreviousTimeStepInfo(float t)
{
AllTimeStepInfo::reverse_iterator i = m_time_step_info.rbegin();
while (i != m_time_step_info.rend() && (*i)->getTime() > t)
i++;
return *i;
} // findClosestTimeStepInfo
AllTimeStepInfo::iterator i = m_time_step_info.end();
while(i!=m_time_step_info.begin())
{
i--;
if ((*i)->getTime() <= t) return i;
}
return i;
} // findPreviousTimeStepInfo
// ----------------------------------------------------------------------------
/** A compare function used when sorting the event lists. It sorts events by
@ -137,8 +146,11 @@ bool RewindQueue::_TimeStepInfoCompare::operator()(const TimeStepInfo * const ri
*/
void RewindQueue::insertRewindInfo(RewindInfo *ri)
{
TimeStepInfo *bucket = findClosestTimeStepInfo(ri->getTime());
bucket->insert(ri);
// FIXME: this should always be the last element in the list(??)
AllTimeStepInfo::iterator bucket = findPreviousTimeStepInfo(ri->getTime());
Log::verbose("RewindQueue", "Insert rewind from %f at %f",
ri->getTime(), (*bucket)->getTime());
(*bucket)->insert(ri);
} // insertRewindInfo
// ----------------------------------------------------------------------------
@ -165,12 +177,12 @@ void RewindQueue::addLocalEvent(EventRewinder *event_rewinder,
* \param confirmed If this state is confirmed to be correct (e.g. is
* being received from the servrer), or just a local state for
* faster rewinds.
* \param time Time at which the state was captured.
*/
void RewindQueue::addLocalState(Rewinder *rewinder, BareNetworkString *buffer,
bool confirmed)
bool confirmed, float time)
{
RewindInfo *ri = new RewindInfoState(World::getWorld()->getTime(), rewinder,
buffer, confirmed);
RewindInfo *ri = new RewindInfoState(time, rewinder, buffer, confirmed);
assert(ri);
insertRewindInfo(ri);
} // addLocalState
@ -216,48 +228,81 @@ void RewindQueue::addNetworkState(Rewinder *rewinder, BareNetworkString *buffer,
// ----------------------------------------------------------------------------
/** Merges thread-safe all data received from the network with the current
* local rewind information.
* \param world_time Current world time up to which network events will be
* merged in.
* \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)
void RewindQueue::mergeNetworkData(float world_time, float dt,
bool *needs_rewind, float *rewind_time)
{
*needs_rewind = false;
m_network_events.lock();
Log::verbose("RewindQueue", "#events in queue at %f %f are %d",
world_time, dt, m_network_events.getData().size());
if(m_network_events.getData().empty())
{
m_network_events.unlock();
return;
}
/** Merge all newly received network events into the main
* event list */
/* Merge all newly received network events into the main event list. */
*rewind_time = 99999.9f;
bool adjust_next = false;
float world_time = World::getWorld()->getTime();
AllNetworkRewindInfo::iterator i;
for (i = m_network_events.getData().begin();
i != m_network_events.getData().end(); ++i)
// FIXME: making m_network_events sorted would prevent the need to
// go through the whole list of events
AllNetworkRewindInfo::iterator i = m_network_events.getData().begin();
while( i!=m_network_events.getData().end() )
{
// Check if a rewind is necessary
float t= (*i)->getTime();
if (t < world_time)
// Ignore any events that will happen in the future.
if ((*i)->getTime() > world_time+dt)
{
*needs_rewind = true;
if (t < *rewind_time) *rewind_time = t;
i++;
continue;
}
TimeStepInfo *tsi = findClosestTimeStepInfo(t);
// FIXME We could try to find the closest TimeStepInfo, or even
// perhaps add a new TimeStepInfo if this new entry is
// 'close to the middle'.
// Find closest previous time step.
AllTimeStepInfo::iterator prev =
findPreviousTimeStepInfo((*i)->getTime());
AllTimeStepInfo::iterator next = prev;
next++;
float event_time = (*i)->getTime();
TimeStepInfo *tsi;
// Assign this event to the closest of the two existing timesteps
// prev and next (inserting an additional event in the past would
// mean more CPU work in the rewind this will very likely trigger.
if (next == m_time_step_info.end())
tsi = *prev;
else if ( (*next)->getTime()-event_time < event_time-(*prev)->getTime() )
tsi = *next;
else
tsi = *prev;
tsi->insert(*i);
Log::info("Rewind", "Inserting event from time %f to timstepinfo %f prev %f next %f",
(*i)->getTime(), tsi->getTime(),
(*prev)->getTime(),
next != m_time_step_info.end() ? (*next)->getTime() : 9999 );
// Check if a rewind is necessary
if (tsi->getTime() < world_time)
{
*needs_rewind = true;
if (tsi->getTime() < *rewind_time) *rewind_time = tsi->getTime();
}
i = m_network_events.getData().erase(i);
} // for i in m_network_events
m_network_events.getData().clear();
m_network_events.unlock();
} // mergeNetworkData
@ -336,42 +381,9 @@ void RewindQueue::undoUntil(float undo_time)
} // undoUntil
#ifdef XXXXXXXXXXXXXXX
// ----------------------------------------------------------------------------
/** 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, 0.1f);
break;
}
case 2:
addTimeEvent(0.0f, 0.1f);
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);
#ifdef XX
// Now test if the three events are sorted in the right order:
// State, then time, then event
assert(hasMoreRewindInfo());
@ -405,64 +417,7 @@ void RewindQueue::testingSortingOrderType(EventRewinder *rewinder, int types[3])
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, 0.1f);
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], 0.1f);
break;
}
case 2:
addTimeEvent(times[i], 0.1f);
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 = getCurrent();
operator++();
while (hasMoreRewindInfo())
{
RewindInfo *ri = getCurrent();
assert(ri_prev->getTime() < ri->getTime());
ri_prev = ri;
operator++();
} // while hasMoreRewindInfo
} // testingSortingOrderTime
#endif
// ----------------------------------------------------------------------------
/** Unit tests for RewindQueue. It tests:
* - Sorting order of RewindInfos at the same time (i.e. state before time
@ -494,53 +449,70 @@ void RewindQueue::unitTesting()
RewindQueue q0;
assert(q0.isEmpty());
assert(!q0.hasMoreRewindInfo());
// 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;
}
q0.addNewTimeStep(0.0f, 0.5f);
q0.m_current = q0.m_time_step_info.begin();
assert(!q0.isEmpty());
assert(q0.hasMoreRewindInfo());
assert(q0.m_time_step_info.size() == 1);
assert((*q0.m_time_step_info.begin())->getNumberOfEvents() == 0);
q0.addLocalState(NULL, NULL, true, 0.0f);
assert((*q0.m_time_step_info.begin())->getNumberOfEvents() == 1);
q0.addNewTimeStep(1.0f, 0.5f);
assert(q0.m_time_step_info.size() == 2);
q0.addNetworkEvent(dummy_rewinder, NULL, 0.0f);
assert((*q0.m_time_step_info.begin())->getNumberOfEvents() == 1);
bool needs_rewind;
float rewind_time;
float world_time = 0.0f;
float dt = 0.01f;
q0.mergeNetworkData(world_time, dt, &needs_rewind, &rewind_time);
assert((*q0.m_time_step_info.begin())->getNumberOfEvents() == 2);
// This will add a third TimeStep at t=0.5
q0.addNetworkEvent(dummy_rewinder, NULL, 0.5f);
q0.mergeNetworkData(world_time, dt, &needs_rewind, &rewind_time);
assert(q0.m_time_step_info.size() == 3);
// This event will get added to the last time step info at 1.0:
q0.addNetworkEvent(dummy_rewinder, NULL, 1.0f);
q0.mergeNetworkData(world_time, dt, &needs_rewind, &rewind_time);
// Note that end() is behind the list, i.e. invalid, but rbegin()
// is the last element
assert((*q0.m_time_step_info.rbegin())->getNumberOfEvents() == 1);
q0.addNetworkEvent(dummy_rewinder, NULL, 1.5f);
q0.mergeNetworkData(world_time, dt, &needs_rewind, &rewind_time);
assert(q0.m_time_step_info.size() == 4);
// Add events within the thresold size to the last event and make sure
// no new time step info is created, but the events are all added to the
// last entry:
q0.addNetworkEvent(dummy_rewinder, NULL,
1.5f - stk_config->m_network_combine_threshold / 2.0f);
q0.addNetworkEvent(dummy_rewinder, NULL,
1.5f + stk_config->m_network_combine_threshold / 2.0f);
q0.mergeNetworkData(world_time, dt, &needs_rewind, &rewind_time);
assert(q0.m_time_step_info.size() == 4);
assert((*q0.m_time_step_info.rbegin())->getNumberOfEvents() == 3);
// 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, 0.1f);
b1.addNewTimeStep(1.0f, 0.1f);
++b1; // Should now point at end of list
b1.hasMoreRewindInfo();
b1.addEvent(NULL, new BareNetworkString(), true, 2.0f);
RewindInfo *ri = b1.getCurrent();
assert(ri->getTime() == 2.0f);
assert(ri->isEvent());
} // unitTesting
b1.addNewTimeStep(2.0f, 0.1f);
TimeStepInfo *tsi = b1.getCurrent();
assert(tsi->getTime() == 2.0f);
#ifdef XX
#endif
void RewindQueue::unitTesting()
{
}
} // unitTesting

View File

@ -55,7 +55,7 @@ private:
* always be at the same time as World::getTime(). */
AllTimeStepInfo::iterator m_current;
TimeStepInfo *findClosestTimeStepInfo(float t);
AllTimeStepInfo::iterator findPreviousTimeStepInfo(float t);
void insertRewindInfo(RewindInfo *ri);
struct _TimeStepInfoCompare
@ -77,12 +77,13 @@ public:
void addLocalEvent(EventRewinder *event_rewinder, BareNetworkString *buffer,
bool confirmed, float time);
void addLocalState(Rewinder *rewinder, BareNetworkString *buffer,
bool confirmed);
bool confirmed, float time);
void addNetworkEvent(EventRewinder *event_rewinder,
BareNetworkString *buffer, float time);
void addNetworkState(Rewinder *rewinder, BareNetworkString *buffer,
float time, float dt);
void mergeNetworkData(bool *needs_rewind, float *rewind_time);
void mergeNetworkData(float world_time, float dt,
bool *needs_rewind, float *rewind_time);
bool isEmpty() const;
bool hasMoreRewindInfo() const;
void undoUntil(float undo_time);

View File

@ -29,8 +29,12 @@ TimeStepInfo::TimeStepInfo(float time, float dt)
{
m_time = time;
m_dt = dt;
m_local_physics_time = Physics::getInstance()->getPhysicsWorld()
->getLocalTime();
// In case of unit testing physics does not exist
if (Physics::getInstance())
m_local_physics_time = Physics::getInstance()->getPhysicsWorld()
->getLocalTime();
else
m_local_physics_time = 0.0f;
} // StateEventList
// --------------------------------------------------------------------
@ -55,7 +59,7 @@ void TimeStepInfo::insert(RewindInfo *ri)
// Events at the same time are just added to the end
m_list_of_events.push_back(ri);
}
} // addStaet
} // insert
// --------------------------------------------------------------------
/** Undos all events and states for this time step.
@ -68,6 +72,7 @@ void TimeStepInfo::undoAll()
(*i)->undo();
}
} // undoAll
// --------------------------------------------------------------------
/** Replays all events for this TimeStepInfo.
*/

View File

@ -62,12 +62,18 @@ private:
* 60 fps. Restoring this value exactly improves accuracy of rewinds. */
float m_local_physics_time;
public:
TimeStepInfo(float time, float dt);
TimeStepInfo(float time, float dt);
void insert(RewindInfo *ri);
void undoAll();
void replayAllEvents();
void replayAllStates();
// ------------------------------------------------------------------------
/** Sets the tiem of this object. */
void setTime(float time) { m_time = time; }
// ------------------------------------------------------------------------
/** Sets the time step size of this object. */
void setDT(float dt) { m_dt = dt; }
// ------------------------------------------------------------------------
/** Returns the time for this TimeStepInfo instance. */
float getTime() const { return m_time; }
// ------------------------------------------------------------------------
@ -86,6 +92,10 @@ public:
const RewindInfo *ri = m_list_of_events[0];
return ri->isState() && ri->isConfirmed();
} // hasConfirmedState
// ------------------------------------------------------------------------
/** Returns the number of events (and states) at this time step. Used
* in unit testing. */
int getNumberOfEvents() const { return m_list_of_events.size(); }
}; // TimeStepInfo
#endif