Started to work on rewinding. ATM a single kart can be rewound

to a previous time (i.e. the race continuous from the previous
location). No memory handling is done, ... all work in progress :)


git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/branches/rewind@13468 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
hikerstk 2013-08-12 23:29:21 +00:00
parent c787926e04
commit 9fe8d0f259
14 changed files with 904 additions and 27 deletions

View File

@ -97,8 +97,8 @@ src/items/projectile_manager.cpp
src/items/rubber_ball.cpp
src/items/rubber_band.cpp
src/items/swatter.cpp
src/karts/abstract_kart_animation.cpp
src/karts/abstract_kart.cpp
src/karts/abstract_kart_animation.cpp
src/karts/cannon_animation.cpp
src/karts/controller/ai_base_controller.cpp
src/karts/controller/ai_properties.cpp
@ -113,6 +113,7 @@ src/karts/kart_gfx.cpp
src/karts/kart_model.cpp
src/karts/kart_properties.cpp
src/karts/kart_properties_manager.cpp
src/karts/kart_rewinder.cpp
src/karts/kart_with_stats.cpp
src/karts/max_speed.cpp
src/karts/moveable.cpp
@ -144,6 +145,8 @@ src/network/network_manager.cpp
src/network/race_info_message.cpp
src/network/race_result_message.cpp
src/network/race_state.cpp
src/network/rewind_manager.cpp
src/network/rewinder.cpp
src/physics/btKart.cpp
src/physics/btKartRaycast.cpp
src/physics/btUprightConstraint.cpp
@ -187,13 +190,13 @@ src/states_screens/help_screen_4.cpp
src/states_screens/kart_selection.cpp
src/states_screens/main_menu_screen.cpp
src/states_screens/options_screen_audio.cpp
src/states_screens/options_screen_input2.cpp
src/states_screens/options_screen_input.cpp
src/states_screens/options_screen_input2.cpp
src/states_screens/options_screen_players.cpp
src/states_screens/options_screen_ui.cpp
src/states_screens/options_screen_video.cpp
src/states_screens/race_gui_base.cpp
src/states_screens/race_gui.cpp
src/states_screens/race_gui_base.cpp
src/states_screens/race_gui_overworld.cpp
src/states_screens/race_result_gui.cpp
src/states_screens/race_setup_screen.cpp
@ -253,8 +256,8 @@ src/animations/animation_base.hpp
src/animations/ipo.hpp
src/animations/three_d_animation.hpp
src/audio/dummy_sfx.hpp
src/audio/music_dummy.hpp
src/audio/music.hpp
src/audio/music_dummy.hpp
src/audio/music_information.hpp
src/audio/music_manager.hpp
src/audio/music_ogg.hpp
@ -262,8 +265,8 @@ src/audio/sfx_base.hpp
src/audio/sfx_buffer.hpp
src/audio/sfx_manager.hpp
src/audio/sfx_openal.hpp
src/challenges/challenge_data.hpp
src/challenges/challenge.hpp
src/challenges/challenge_data.hpp
src/challenges/game_slot.hpp
src/challenges/unlock_manager.hpp
src/config/device_config.hpp
@ -305,11 +308,11 @@ src/guiengine/scalable_font.hpp
src/guiengine/screen.hpp
src/guiengine/skin.hpp
src/guiengine/widget.hpp
src/guiengine/widgets.hpp
src/guiengine/widgets/bubble_widget.hpp
src/guiengine/widgets/button_widget.hpp
src/guiengine/widgets/check_box_widget.hpp
src/guiengine/widgets/dynamic_ribbon_widget.hpp
src/guiengine/widgets.hpp
src/guiengine/widgets/icon_button_widget.hpp
src/guiengine/widgets/label_widget.hpp
src/guiengine/widgets/list_widget.hpp
@ -321,8 +324,8 @@ src/guiengine/widgets/spinner_widget.hpp
src/guiengine/widgets/text_box_widget.hpp
src/input/binding.hpp
src/input/device_manager.hpp
src/input/input_device.hpp
src/input/input.hpp
src/input/input_device.hpp
src/input/input_manager.hpp
src/input/wiimote.hpp
src/input/wiimote_manager.hpp
@ -344,8 +347,8 @@ src/items/projectile_manager.hpp
src/items/rubber_ball.hpp
src/items/rubber_band.hpp
src/items/swatter.hpp
src/karts/abstract_kart_animation.hpp
src/karts/abstract_kart.hpp
src/karts/abstract_kart_animation.hpp
src/karts/cannon_animation.hpp
src/karts/controller/ai_base_controller.hpp
src/karts/controller/ai_properties.hpp
@ -356,11 +359,12 @@ src/karts/controller/player_controller.hpp
src/karts/controller/skidding_ai.hpp
src/karts/explosion_animation.hpp
src/karts/ghost_kart.hpp
src/karts/kart_gfx.hpp
src/karts/kart.hpp
src/karts/kart_gfx.hpp
src/karts/kart_model.hpp
src/karts/kart_properties.hpp
src/karts/kart_properties_manager.hpp
src/karts/kart_rewinder.hpp
src/karts/kart_with_stats.hpp
src/karts/max_speed.hpp
src/karts/moveable.hpp
@ -400,6 +404,8 @@ src/network/race_result_message.hpp
src/network/race_start_message.hpp
src/network/race_state.hpp
src/network/remote_kart_info.hpp
src/network/rewind_manager.hpp
src/network/rewinder.hpp
src/network/world_loaded_message.hpp
src/physics/btKart.hpp
src/physics/btKartRaycast.hpp
@ -447,13 +453,13 @@ src/states_screens/help_screen_4.hpp
src/states_screens/kart_selection.hpp
src/states_screens/main_menu_screen.hpp
src/states_screens/options_screen_audio.hpp
src/states_screens/options_screen_input2.hpp
src/states_screens/options_screen_input.hpp
src/states_screens/options_screen_input2.hpp
src/states_screens/options_screen_players.hpp
src/states_screens/options_screen_ui.hpp
src/states_screens/options_screen_video.hpp
src/states_screens/race_gui_base.hpp
src/states_screens/race_gui.hpp
src/states_screens/race_gui_base.hpp
src/states_screens/race_gui_overworld.hpp
src/states_screens/race_result_gui.hpp
src/states_screens/race_setup_screen.hpp
@ -483,8 +489,8 @@ src/tracks/check_sphere.hpp
src/tracks/check_structure.hpp
src/tracks/graph_node.hpp
src/tracks/lod_node_loader.hpp
src/tracks/quad_graph.hpp
src/tracks/quad.hpp
src/tracks/quad_graph.hpp
src/tracks/quad_set.hpp
src/tracks/terrain_info.hpp
src/tracks/track.hpp

View File

@ -28,6 +28,7 @@
#include "modes/demo_world.hpp"
#include "modes/profile_world.hpp"
#include "modes/world.hpp"
#include "network/rewind_manager.hpp"
#include "physics/physics.hpp"
#include "race/history.hpp"
#include "replay/replay_recorder.hpp"
@ -92,6 +93,8 @@ InputManager::~InputManager()
void InputManager::handleStaticAction(int key, int value)
{
static bool control_is_pressed = false;
static bool shift_is_pressed = false;
World *world = World::getWorld();
// When no players... a cutscene
@ -131,7 +134,10 @@ void InputManager::handleStaticAction(int key, int value)
case KEY_LWIN:
control_is_pressed = value!=0;
break;
case KEY_LSHIFT:
case KEY_RSHIFT:
case KEY_SHIFT:
shift_is_pressed = value!=0; break;
case KEY_KEY_I:
{
if (!world || !UserConfigParams::m_artist_debug_mode) break;
@ -161,7 +167,18 @@ void InputManager::handleStaticAction(int key, int value)
break;
case KEY_F1:
if (UserConfigParams::m_artist_debug_mode && world)
if(value && shift_is_pressed && world && RewindManager::isEnabled())
{
printf("Enter rewind time:");
char s[256];
fgets(s, 256, stdin);
float t;
StringUtils::fromString(s,t);
RewindManager::get()->rewindTo(world->getTime()-t);
Log::info("Rewind", "Rewinding from %f to %f",
world->getTime(), world->getTime()-t);
}
else if (UserConfigParams::m_artist_debug_mode && world)
{
AbstractKart* kart = world->getLocalPlayerKart(0);

View File

@ -422,6 +422,8 @@ void RubberBall::moveTowardsTarget(Vec3 *next_xyz, float dt)
// at it directly, stop interpolating, instead fly straight
// towards it.
Vec3 diff = m_target->getXYZ()-getXYZ();
if(diff.length()==0)
printf("diff=0\n");
*next_xyz = getXYZ() + (dt*m_speed/diff.length())*diff;
Vec3 old_vec = getXYZ()-m_previous_xyz;

View File

@ -21,6 +21,8 @@
#include "network/message.hpp"
#include <string.h>
/**
* \ingroup controller
*/
@ -73,6 +75,21 @@ public:
m_fire = false;
m_look_back = false;
} // reset
// ------------------------------------------------------------------------
/** Tests if two KartControls are equal.
*/
bool operator==(const KartControl &other)
{
return m_steer == other.m_steer &&
m_accel == other.m_accel &&
m_brake == other.m_brake &&
m_nitro == other.m_nitro &&
m_skid == other.m_skid &&
m_rescue == other.m_rescue &&
m_fire == other.m_fire &&
m_look_back == other.m_look_back;
} // operator==
// ------------------------------------------------------------------------
/** Return the serialised size in bytes. */
static int getLength() { return 9; }
@ -84,6 +101,24 @@ public:
m->addFloat(m_accel);
m->addChar(getButtonsCompressed());
} // compress
// ------------------------------------------------------------------------
/** Copies the important data from this objects into a memory buffer. */
void copyToMemory(char *buffer)
{
memcpy(buffer, &m_steer, sizeof(float));
memcpy(buffer+sizeof(float), &m_accel, sizeof(float));
buffer[2*sizeof(float)] = getButtonsCompressed();
} // copyToMemory
// ------------------------------------------------------------------------
/** Restores this object from a previously saved memory buffer. */
void setFromMemory(char *buffer)
{
memcpy(&m_steer, buffer, sizeof(float));
memcpy(&m_accel, buffer+4*sizeof(float), sizeof(float));
setButtonsCompressed(buffer[2*sizeof(float)]);
} // setFromMemory
// ------------------------------------------------------------------------
void uncompress(char *c)
{

View File

@ -19,13 +19,6 @@
#include "karts/kart.hpp"
#include <math.h>
#include <iostream>
#include <algorithm> // for min and max
#include <ICameraSceneNode.h>
#include <ISceneManager.h>
#include "audio/music_manager.hpp"
#include "audio/sfx_manager.hpp"
#include "audio/sfx_base.hpp"
@ -44,6 +37,7 @@
#include "guiengine/scalable_font.hpp"
#include "karts/explosion_animation.hpp"
#include "karts/kart_gfx.hpp"
#include "karts/kart_rewinder.hpp"
#include "karts/rescue_animation.hpp"
#include "modes/overworld.hpp"
#include "modes/world.hpp"
@ -71,6 +65,13 @@
#include "utils/log.hpp" //TODO: remove after debugging is done
#include <math.h>
#include <iostream>
#include <algorithm> // for min and max
#include <ICameraSceneNode.h>
#include <ISceneManager.h>
#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__MINGW32__)
// Disable warning for using 'this' in base member initializer list
@ -220,6 +221,8 @@ void Kart::init(RaceManager::KartType type)
0.0f) );
reset();
m_rewinder = new KartRewinder(this);
} // init
// ----------------------------------------------------------------------------
@ -229,6 +232,8 @@ void Kart::init(RaceManager::KartType type)
*/
Kart::~Kart()
{
delete m_rewinder;
// Delete all custom sounds (TODO: add back when properly done)
/*
for (int n = 0; n < SFXManager::NUM_CUSTOMS; n++)
@ -1055,6 +1060,8 @@ void Kart::eliminate()
*/
void Kart::update(float dt)
{
m_rewinder->update();
if ( UserConfigParams::m_graphical_effects )
{
// update star effect (call will do nothing if stars are not activated)

View File

@ -34,15 +34,16 @@
#include "tracks/terrain_info.hpp"
#include "utils/no_copy.hpp"
class AbstractKartAnimation;
class Attachment;
class btKart;
class btUprightConstraint;
class Attachment;
class Controller;
class Item;
class AbstractKartAnimation;
class HitEffect;
class Item;
class KartGFX;
class KartRewinder;
class MaxSpeed;
class ParticleEmitter;
class ParticleKind;
@ -198,6 +199,9 @@ private:
float m_view_blocked_by_plunger;
float m_speed;
/** The rewinder object for network play. */
KartRewinder *m_rewinder;
std::vector<SFXBase*> m_custom_sounds;
SFXBase *m_beep_sound;
SFXBase *m_engine_sound;

View File

@ -0,0 +1,96 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2013 Joerg Henrichs
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "karts/kart_rewinder.hpp"
#include "karts/abstract_kart.hpp"
#include "modes/world.hpp"
#include "network/rewind_manager.hpp"
#include "utils/vec3.hpp"
#include <string.h>
KartRewinder::KartRewinder(AbstractKart *kart) : Rewinder(/*can_be_destroyed*/ false)
{
m_kart = kart;
} // KartRewinder
// ----------------------------------------------------------------------------
/** Saves all state information for a kart in a memory buffer. The memory
* is allocated here and the address returned. It will then be managed
* by the RewindManager. The size is used to keep track of memory usage
* for rewinding.
* \param[out] buffer Address of the memory buffer.
* \returns Size of allocated memory, or -1 in case of an error.
*/
int KartRewinder::getState(char **buffer) const
{
const int MEMSIZE = 13*sizeof(float);
*buffer = new char[MEMSIZE];
float* p = (float*)*buffer;
if(!buffer)
{
Log::error("KartRewinder", "Can not allocate %d bytes.", MEMSIZE);
return -1;
}
const btRigidBody *body = m_kart->getBody();
const btTransform &t = body->getWorldTransform();
btQuaternion q = t.getRotation();
memcpy(p+ 0, t.getOrigin(), 3*sizeof(float));
memcpy(p+ 3, &q, 4*sizeof(float));
memcpy(p+ 7, body->getLinearVelocity(), 3*sizeof(float));
memcpy(p+10, body->getAngularVelocity(), 3*sizeof(float));
return MEMSIZE;
} // getState
// ----------------------------------------------------------------------------
/** Called once a frame. It will add a new kart control event to the rewind
* manager if any control values have changed.
*/
void KartRewinder::update()
{
if(m_kart->getControls() == m_previous_control)
return;
m_previous_control = m_kart->getControls();
char *buffer = new char[m_previous_control.getLength()];
m_previous_control.copyToMemory(buffer);
// The rewind manager will free the memory once it's not needed anymore
RewindManager::get()->addEvent(this, World::getWorld()->getTime(), buffer);
} // update
// ----------------------------------------------------------------------------
/** Actuall rewind to the specified state. */
void KartRewinder::rewindToState(char *buffer)
{
btTransform t;
float *p = (float*)buffer;
t.setOrigin(*(btVector3*)p);
t.setRotation(*(btQuaternion*)(p+3));
btRigidBody *body = m_kart->getBody();
body->proceedToTransform(t);
body->setLinearVelocity(*(btVector3*)(p+7));
body->setAngularVelocity(*(btVector3*)(p+10));
return;
} // rewindToState

View File

@ -0,0 +1,63 @@
//
// 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.
#ifndef HEADER_KART_REWINDER_HPP
#define HEADER_KART_REWINDER_HPP
#include "karts/controller/kart_control.hpp"
#include "network/rewinder.hpp"
#include "utils/cpp2011.h"
class AbstractKart;
class KartRewinder : public Rewinder
{
private:
/** Pointer to the original kart object. */
AbstractKart *m_kart;
KartControl m_previous_control;
public:
KartRewinder(AbstractKart *kart);
virtual ~KartRewinder() {};
virtual int getState(char **buffer) const;
virtual void rewindToState(char *p) OVERRIDE;
virtual void rewindToEvent(char *p) OVERRIDE
{
}; // rewindToEvent
// -------------------------------------------------------------------------
virtual void undoState(char *p) OVERRIDE
{
}; // undoState
// -------------------------------------------------------------------------
virtual void undoEvent(char *p) OVERRIDE
{
}; // undoEvent
// -------------------------------------------------------------------------
void update();
}; // Rewinder
#endif

View File

@ -168,6 +168,7 @@
#include "modes/demo_world.hpp"
#include "modes/profile_world.hpp"
#include "network/network_manager.hpp"
#include "network/rewind_manager.hpp"
#include "race/grand_prix_manager.hpp"
#include "race/highscore_manager.hpp"
#include "race/history.hpp"
@ -590,7 +591,7 @@ int handleCmdLinePreliminary(int argc, char **argv)
} // --verbose or -v
}
return 0;
}
} // handleCmdLinePreliminary
// ============================================================================
/** Handles command line options.
@ -638,6 +639,10 @@ int handleCmdLine(int argc, char **argv)
{
UserConfigParams::m_camera_debug=1;
}
else if( !strcmp(argv[i], "--rewind") )
{
RewindManager::setEnable(true);
}
else if(UserConfigParams::m_artist_debug_mode &&
!strcmp(argv[i], "--physics-debug"))
{

View File

@ -44,6 +44,7 @@
#include "modes/profile_world.hpp"
#include "network/network_manager.hpp"
#include "network/race_state.hpp"
#include "network/rewind_manager.hpp"
#include "physics/btKart.hpp"
#include "physics/physics.hpp"
#include "physics/triangle_mesh.hpp"
@ -148,6 +149,8 @@ void World::init()
// constructor is called, so the wrong race gui would be created.
createRaceGUI();
RewindManager::create();
// Grab the track file
m_track = track_manager->getTrack(race_manager->getTrackName());
if(!m_track)
@ -201,6 +204,8 @@ void World::init()
*/
void World::reset()
{
RewindManager::get()->reset();
// If m_saved_race_gui is set, it means that the restart was done
// when the race result gui was being shown. In this case restore the
// race gui (note that the race result gui is cached and so never really
@ -346,6 +351,8 @@ Controller* World::loadAIController(AbstractKart *kart)
//-----------------------------------------------------------------------------
World::~World()
{
RewindManager::destroy();
if(ReplayPlay::get())
{
// Destroy the old replay object, which also stored the ghost
@ -844,7 +851,9 @@ void World::update(float dt)
projectile_manager->update(dt);
PROFILER_POP_CPU_MARKER();
RewindManager::get()->update(dt);
PROFILER_POP_CPU_MARKER();
#ifdef DEBUG
assert(m_magic_number == 0xB01D6543);

View File

@ -0,0 +1,333 @@
//
// 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_manager.hpp"
#include "modes/world.hpp"
#include "network/rewinder.hpp"
#include "utils/log.hpp"
RewindManager* RewindManager::m_rewind_manager = NULL;
bool RewindManager::m_enable_rewind_manager = false;
/** Creates the singleton. */
RewindManager *RewindManager::create()
{
assert(!m_rewind_manager);
m_rewind_manager = new RewindManager();
return m_rewind_manager;
} // create
// ----------------------------------------------------------------------------
/** Destroys the singleton. */
void RewindManager::destroy()
{
assert(m_rewind_manager);
delete m_rewind_manager;
m_rewind_manager = NULL;
} // destroy
// ============================================================================
/** Constructor for a state: it only takes the size, and allocates a buffer
* for all state info.
* \param size Necessary buffer size for a state.
*/
RewindManager::RewindInfo::RewindInfo(Rewinder *rewinder, float time,
char *buffer, bool is_event,
bool is_confirmed)
{
m_rewinder = rewinder;
m_time = time;
m_buffer = buffer;
m_is_event = is_event;
m_is_confirmed = is_confirmed;
} // RewindInfo
// ============================================================================
/** The constructor.
*/
RewindManager::RewindManager()
{
reset();
} // RewindManager
// ----------------------------------------------------------------------------
/** Frees all saved state information. Note that the Rewinder data must be
* freed elsewhere.
*/
RewindManager::~RewindManager()
{
// Destroying the
for(unsigned int i=0; i<m_rewind_info.size(); i++)
{
delete m_rewind_info[i];
}
m_rewind_info.clear();
} // ~RewindManager
// ----------------------------------------------------------------------------
/** Frees all saved state information and all destroyable rewinder.
*/
void RewindManager::reset()
{
#ifdef REWIND_SEARCH_STATS
m_count_of_comparisons = 0;
m_count_of_searches = 0;
#endif
m_overall_state_size = 0;
if(!m_enable_rewind_manager) return;
AllRewinder::iterator r = m_all_rewinder.begin();
while(r!=m_all_rewinder.end())
{
if(!(*r)->canBeDestroyed())
{
r++;
continue;
}
Rewinder *rewinder = *r;
r = m_all_rewinder.erase(r);
// FIXME Do we really want to delete this here?
delete rewinder;
}
for(unsigned int i=0; i<m_rewind_info.size(); i++)
{
delete m_rewind_info[i];
}
m_rewind_info.clear();
} // reset
// ----------------------------------------------------------------------------
void RewindManager::insertRewindInfo(RewindInfo *ri)
{
#ifdef REWIND_SEARCH_STATS
m_count_of_searches++;
#endif
float t = ri->getTime();
if(ri->isEvent())
{
// If there are several infos for the same time t,
// events must be inserted at the end
AllRewindInfo::reverse_iterator i = m_rewind_info.rbegin();
while(i!=m_rewind_info.rend() &&
(*i)->getTime() > t)
{
#ifdef REWIND_SEARCH_STATS
m_count_of_comparisons++;
#endif
i++;
}
AllRewindInfo::iterator insert_point = i.base();
m_rewind_info.insert(insert_point,ri);
return;
}
else // is a states
{
// If there are several infos for the same time t,
// a state must be inserted first
AllRewindInfo::reverse_iterator i = m_rewind_info.rbegin();
while(i!=m_rewind_info.rend() && (*i)->getTime() >= t)
{
#ifdef REWIND_SEARCH_STATS
m_count_of_comparisons++;
#endif
i++;
}
AllRewindInfo::iterator insert_point = i.base();
m_rewind_info.insert(insert_point,ri);
return;
}
} // insertRewindData
// ----------------------------------------------------------------------------
/** Returns the first (i.e. lowest) index i in m_rewind_info which fulfills
* time(i) < target_time <= time(i+1)
* This is used to determine the starting point from which to rewind.
* \param time Time for which an index is searched.
* \return Index in m_rewind_info after which to add rewind data.
*/
unsigned int RewindManager::findFirstIndex(float target_time) const
{
// 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. Note that after finding an entry in a binary
// search, you stil 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
int index = m_rewind_info.size()-1;
while(index>=0)
{
#ifdef REWIND_SEARCH_STATS
m_count_of_comparisons++;
#endif
if(m_rewind_info[index]->getTime()<target_time)
return index;
index--;
}
// For now just exit here
Log::fatal("RewindManager",
"Inserting before first state at %f, insert at %f.",
m_rewind_info[0]->getTime(), target_time);
return 0; // 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!
* \param time Time at which the event was recorded.
* \param buffer Pointer to the event data.
*/
void RewindManager::addEvent(Rewinder *rewinder, float time, char *buffer)
{
RewindInfo *ri = new RewindInfo(rewinder, time, buffer, /*is_event*/true,
/*is_confirmed*/true);
insertRewindInfo(ri);
} // addEvent
// ----------------------------------------------------------------------------
/** Determines if a new state snapshot should be taken, and if so calls all
* rewinder to do so.
* \param dt Time step size.
*/
void RewindManager::update(float dt)
{
if(!m_enable_rewind_manager || m_all_rewinder.size()==0) return;
float time = World::getWorld()->getTime();
// For now always create a snapshot.
for(unsigned int i=0; i<m_all_rewinder.size(); i++)
{
char *p;
int size = m_all_rewinder[i]->getState(&p);
if(size>=0)
{
m_overall_state_size += size;
RewindInfo *ri = new RewindInfo(m_all_rewinder[i], time, p,
/*is_event*/false,
/*is_confirmed*/true);
assert(ri);
insertRewindInfo(ri);
} // size >= 0
}
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) );
} // update
// ----------------------------------------------------------------------------
/** Rewinds to the specified time.
* \param t Time to rewind to.
*/
void RewindManager::rewindTo(float rewind_time)
{
// First find the state to which we need to rewind
// ------------------------------------------------
int state = findFirstIndex(rewind_time);
if(m_rewind_info[state]->isEvent())
{
Log::error("RewindManager", "No state for rewind to %d, state %d.",
rewind_time, state);
return;
}
// Then undo the states that are skipped
// -------------------------------------
for(int i=m_rewind_info.size()-1; i>(int)state; i--)
{
m_rewind_info[i]->undo();
} // for i>state
// TODO: we need some logic here to handle that confirmed states are not
// necessarily the same for each rewinder. So if we rewind to t, one
// rewinder forces us to go back to t1 (latest state saved before t),
// another one to t2.
// So we have to detect the latest time t_min < t for which each
// rewinder has a confirmed state. Then we rewind from t_min. But if we
// find another confirmed state for a rewinder at time t1 (t_min<t1<t),
// we have to overwrite the current state of the rewinder with that at
// time t1.
// Also care needs to be taken with events: e.g. either we make steering
// information (for kars) part of the state, or we have to go even further
// back in time (before t_min) to find what state steering was in at time
// t_min.
// Rewind to the required state
// ----------------------------
float current_time = m_rewind_info[state]->getTime();
// Rewind all objects (and also all state) that happen at the
// current time.
// TODO: this assumes atm that all rewinder have a initial state
// for 'current_time'!!!
while(m_rewind_info[state]->getTime()==current_time)
{
m_rewind_info[state]->rewind();
state ++;
} // while rewind info at time current_time
// Store the time to which we have to replay to
float current_time = World::getWorld()->getTime();
World::setTime(current_time);
// Now go forward through the saved states, and
// replay taking the events into account.
while(World::getWorld()->getTime() < current_time)
{
// Find the next event which needs to be taken into account
// All other states can be deleted
// TODO ... for now
while(state < m_rewind_info.size() && !m_rewind_info[state]->isEvent())
state ++;
float dt = determineTimeStepSize(state);
World::getWorld()->update(dt);
}
} // 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 state The next state to replay.
* \return The time step size to use in the next simulation step.
*/
float RewindManager::determineTimeStepSize(int state)
{
float dt = 1.0f/60.0f;
float t = World::getWorld()->getTime();
if(m_rewind_info[state]->getTime() < t + dt)
return t-m_rewind_info[state]->getTime();
return dt;
} // determineTimeStepSize

View File

@ -0,0 +1,195 @@
//
// 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.
#ifndef HEADER_REWIND_MANAGER_HPP
#define HEADER_REWIND_MANAGER_HPP
#include "network/rewinder.hpp"
#include "utils/ptr_vector.hpp"
#include <assert.h>
#include <vector>
class RewindManager
{
private:
/** Singleton pointer. */
static RewindManager *m_rewind_manager;
/** En- or Disable the rewind manager. This is used to disable storing
* rewind data in case of local races only. */
static bool m_enable_rewind_manager;
typedef std::vector<Rewinder *> AllRewinder;
/** A list of all objects that can be rewound. */
AllRewinder m_all_rewinder;
/** Overall amount of memory allocated by states. */
unsigned int m_overall_state_size;
// ========================================================================
/** Used to store rewind information for a given time for all rewind
* instances.
* Rewind information can either be a state (for example a kart would
* have position, rotation, linear and angular velocity, ... as state),
* or an event (for a kart that would be pressing or releasing of a key).
* State changes and events can be delivered in different frequencies,
* and might be released (to save memory) differently: A state can be
* reproduced from a previous state by replaying the simulation taking
* all events into account.
*/
class RewindInfo
{
private:
/** Pointer to the buffer which stores all states. */
char *m_buffer;
/** Time when this state was taken. */
float m_time;
/** True if this is an event, and not a state. */
bool m_is_event;
/** A confirmed event is one that was sent from the server. When
* rewinding we have to start with a confirmed state for each
* object. */
bool m_is_confirmed;
/** The Rewinder instance for which this data is. */
Rewinder *m_rewinder;
public:
RewindInfo(Rewinder *rewinder, float time, char *buffer,
bool is_event, bool is_confirmed);
// --------------------------------------------------------------------
~RewindInfo()
{
delete m_buffer;
} // ~RewindInfo
// --------------------------------------------------------------------
/** Returns a pointer to the state buffer. */
char *getBuffer() const { return m_buffer; }
// --------------------------------------------------------------------
/** Returns the time at which this rewind state was saved. */
float getTime() const { return m_time; }
// --------------------------------------------------------------------
bool isEvent() const { return m_is_event; }
// --------------------------------------------------------------------
/** Returns if this state is confirmed. */
bool isConfirmed() const { return m_is_confirmed; }
// --------------------------------------------------------------------
/** Called when going back in time to undo any rewind information.
* It calls either undoEvent or undoState in the rewinder. */
void undo()
{
if(m_is_event)
m_rewinder->undoEvent(m_buffer);
else
m_rewinder->undoState(m_buffer);
} // undoEvent
// --------------------------------------------------------------------
/** Rewinds to this state. This is called while going forwards in time
* again to reach current time. If the info is a state, it will
* call rewindToState(char *) if the state is a confirmed state, or
* rewindReplace(char*) in order to discard the old stored data,
* and replace it with the new state at that time. In case of an
* event, rewindEvent(char*) is called.
*/
void rewind()
{
if(m_is_event)
m_rewinder->rewindToEvent(m_buffer);
else
{
if(m_is_confirmed)
m_rewinder->rewindToState(m_buffer);
else
{
// TODO
// Handle replacing of stored states.
}
}
} // rewind
}; // RewindInfo
// ========================================================================
/** Pointer to all saved states. */
typedef std::vector<RewindInfo*> AllRewindInfo;
AllRewindInfo m_rewind_info;
#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();
unsigned int findFirstIndex(float time) const;
void insertRewindInfo(RewindInfo *ri);
public:
// First static functions to manage rewinding.
// ===========================================
static RewindManager *create();
static void destroy();
// ------------------------------------------------------------------------
/** En- or disables rewinding. */
static void setEnable(bool m) { m_enable_rewind_manager = m;}
// ------------------------------------------------------------------------
/** Returns if rewinding is enabled or not. */
static bool isEnabled() { return m_enable_rewind_manager; }
// ------------------------------------------------------------------------
/** Returns the singleton. This function will not automatically create
* the singleton. */
static RewindManager *get()
{
assert(m_rewind_manager);
return m_rewind_manager;
} // get
// ------------------------------------------------------------------------
void reset();
void update(float dt);
// ------------------------------------------------------------------------
/** Adds a Rewinder to the list of all rewinders.
* \return true If rewinding is enabled, false otherwise.
*/
bool addRewinder(Rewinder *rewinder)
{
if(!m_enable_rewind_manager) return false;
m_all_rewinder.push_back(rewinder);
return true;
} // addRewinder
// ------------------------------------------------------------------------
void rewindTo(float target_time);
// ------------------------------------------------------------------------
void addEvent(Rewinder *rewinder, float time, char *buffer);
}; // RewindManager
#endif

37
src/network/rewinder.cpp Normal file
View File

@ -0,0 +1,37 @@
//
// 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/rewinder.hpp"
#include "network/rewind_manager.hpp"
/** Constructor. It will add this object to the list of all rewindable
* objects in the rewind manager.
*/
Rewinder::Rewinder(bool can_be_destroyed)
{
m_can_be_destroyed = can_be_destroyed;
RewindManager::get()->addRewinder(this);
} // Rewinder
// ----------------------------------------------------------------------------
/** Destructor.
*/
Rewinder::~Rewinder()
{
} // ~Rewinder

68
src/network/rewinder.hpp Normal file
View File

@ -0,0 +1,68 @@
//
// 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.
#ifndef HEADER_REWINDER_HPP
#define HEADER_REWINDER_HPP
class Rewinder
{
private:
bool m_can_be_destroyed;
public:
Rewinder(bool can_be_destroyed);
virtual ~Rewinder();
/** Provides a copy of the state of the object in one memory buffer.
* The memory is managed by the RewindManager.
* \param[out] buffer The address of the memory buffer with the state.
* \return Size of the buffer, or -1 in case of an error.
*/
virtual int getState(char **buffer) const = 0;
/** Called when an event needs to be undone. This is called while going
* backwards for rewinding - all stored events will get an 'undo' call.
* A dummy implementation is provided which just ignores this.
*/
virtual void undoEvent(char *buffer) = 0;
/** Called when an event needs to be replayed. This is called during
* rewind, i.e. when going forward in time again.
*/
virtual void rewindToEvent(char *buffer) = 0;
/** Called when a state needs to be replayed. This is called during
* rewind, i.e. when going forward in time again, and only for confirmed
* states.
*/
virtual void rewindToState(char *buffer) = 0;
/** Undo the effects of the given state, but do not rewind to that
* state (which is done by rewindTo). This is called while going
* backwards for rewinding - all stored events will get an 'undo' call.
* Provided here a dummy implementation that just ignores the state.
*/
virtual void undoState(char *p) = 0;
// -------------------------------------------------------------------------
/** True if this rewinder can be destroyed. Karts can not be destroyed,
* cakes can. This is used by the RewindManager in reset. */
bool canBeDestroyed() const { return m_can_be_destroyed; }
}; // Rewinder
#endif