Merge remote-tracking branch 'origin/network_improvements'

This commit is contained in:
Benau 2018-03-02 17:39:16 +08:00
commit 43cf29af86
135 changed files with 5618 additions and 3283 deletions

View File

@ -15,9 +15,9 @@
<div width="100%" height="fit" layout="horizontal-row" >
<label proportion="1" text_align="left" I18N="In the server creation screen" text="Max. number of players"/>
<gauge id="max_players" proportion="1" min_value="2" max_value="12"/>
<gauge id="max_players" proportion="1" min_value="2"/>
</div>
<spacer height="20" width="20"/>
<div width="100%" height="fit" layout="horizontal-row" >

View File

@ -3,58 +3,34 @@
<div x="0" y="0" width="100%" height="100%" layout="vertical-row" >
<header text_align="center" width="80%" align="center" I18N="In networking lobby" text="Lobby"/>
<spacer height="15" width="10"/>
<div proportion="1" x="2%" width="96%" layout="vertical-row">
<div proportion="4" x="2%" width="96%" layout="vertical-row">
<div width="100%" proportion="2" layout="horizontal-row">
<box id="info" proportion="2" height="100%" layout="vertical-row">
<div width="100%" height="fit" layout="horizontal-row" >
<label proportion="1" text_align="left" I18N="In the networking lobby" text="Server name:"/>
<label proportion="2" text_align="left" id="server_name" text=""/>
</div>
<div width="100%" height="fit" layout="horizontal-row" >
<label proportion="1" text_align="left" I18N="In the networking lobby" text="Difficulty:"/>
<label proportion="2" text_align="left" id="server_difficulty" text=""/>
</div>
<div width="100%" height="fit" layout="horizontal-row" >
<label proportion="1" text_align="left" I18N="In the networking lobby" text="Game mode:"/>
<label proportion="2" text_align="left" id="server_game_mode" text=""/>
</div>
<label word_wrap="true" id="text" proportion="3" width="100%" height="100%" text_valign="top"/>
</box>
<spacer width="20" height="20"/>
<box proportion="1" height="100%" layout="vertical-row">
<list id="players" width="100%" height="100%"/>
</box>
</div>
<spacer width="20" height="20"/>
<div width="100%" proportion="1" layout="horizontal-row">
<box proportion="2" height="100%" layout="vertical-row">
<list id="chat" width="100%" height="100%"/>
</box>
<spacer width="20" height="20"/>
<box id="actions" proportion="1" height="100%" layout="vertical-row">
<!-- <label I18N="In networking lobby" word_wrap="true" text="actions" align="center" text-align="center"/>
-->
<icon-button id="start" width="64" height="64" icon="gui/green_check.png" align="center"
I18N="In the network lobby" text="Start Race"/>
</box>
</div>
</div>
<spacer width="10" height="7%"/>
<bottombar x="2%" width="96%" height="10%" layout="horizontal-row">
<label text_align="left" align="center" height="100%" id="online_status" proportion="1" text=""/>
<spacer width="10" height="10" />
<buttonbar id="menu_bottomrow" x="0" y="0" width="20%" height="100%" align="center">
<icon-button id="exit" width="64" height="64" icon="gui/main_quit.png" extend_label="50"
I18N="In the networking lobby" text="Exit" label_location="hover"/>
<spacer height="10"/>
<div width="100%" proportion="1" layout="horizontal-row">
<spacer width="20" height="20"/>
<box proportion="2" height="100%" layout="vertical-row">
<textbox id="chat" width="100%" height="30%"/>
<spacer height="20"/>
<button id="send_text" height="30%" width="fit" I18N="In the network lobby" text="Send text" />
</box>
<spacer width="40"/>
<buttonbar id="actions" proportion="1" width="75%" height="75%">
<icon-button id="start" width="64" height="64" icon="gui/green_check.png" align="center"
I18N="In the network lobby" text="Start race"/>
<icon-button id="exit" width="64" height="64" icon="gui/main_quit.png" align="center"
I18N="In the network lobby" text="Exit"/>
</buttonbar>
</bottombar>
</div>
<spacer height="10"/>
</div>
<icon-button id="back" x="0" y="0" height="8%" icon="gui/back.png"/>
</stkgui>

View File

@ -92,6 +92,7 @@
case (all three normals discarded, the interpolation will just
return the normal of the triangle (i.e. de facto no interpolation),
but it helps making smoothing much more useful without fixing tracks.
fps: The physics timestep size
default-track-friction: Default friction to be used for the track and
any track/library pbject.
default-moveable-friction: Default friction to be used for any moveable,
@ -99,6 +100,7 @@
-->
<physics smooth-normals="true"
smooth-angle-limit="0.65"
fps="120"
default-track-friction="0.5"
default-moveable-friction="0.5" />
@ -158,9 +160,16 @@
away if there is an explosion. -->
<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"/>
<!-- Networking
state-frequency: how many states the server will send per second.
positional-smoothing: smoothing factor used in exponential smoothing
depending on error.
rotational-smoothing: slerp factor used in exponential smoothing
of rotations depending on error.
-->
<networking state-frequency="10"
positional-smoothing="0.25:0.95 1.0:0.85"
rotational-smoothing="0.25:0.95 1.0:0.85" />
<!-- The field od views for 1-4 player split screen. fov-3 is
actually not used (since 3 player split screen uses the

42
doc/physics_order Normal file
View File

@ -0,0 +1,42 @@
This shows in which order input handling, physics computations and
other kart related items are updated in each frame.
main_loop:
getDT() : Determine nexts DT --> simulation
is [T, T+DT] with T=now.
RewindManager::addNextTimeStep() : Adds a default TimeStepInfo entry to the
RewindQueue which will store events for the
current time step (e.g. key presses, and
network data).
irr_driver::update() : Rendering and input handling.
Controller::action() : Store user action in m_controls of
kart. Clients send event to server.
RaceEventManager::update() : A thin wrapper around world used in networked
races.
RewindManager::playEventsTill() : Plays all events in [T, T+DT]: copies unhandled
network events that must be handled at the
current time to the current TimeStepInfo. Can do
complete rewind and replay till T is reached again!
World::updateWorld()
RewindManager::update() : Store current state on server if necessary and
broadcast it to clients.
Karts::update()
Moveable::update() : Copy physics data from bullet to STK.
updateSpeed() : Get physics speed and set it in kart.
Controller::update() : Set kart steering based on user/AI input.
Slipstream::update() : call Kart::handleZipper if required.
updatePhysics()
HandleStartBoost : Trigger boost if required.
updateEnginePower...() : Sets engine power/brakes for bullet vehicle.
Skidding::update() : Update skidding values (which will
affect steering).
setSteering : Sets the bullet steering based on
kart's current steering.
updateSliding() : Test for sliding which can reduce the wheels
friction/grip, causing the physics to slide.
MaxSpeed::update() : Cap speed of kart if kart is too fast.
!physicsafter : !Print debug values
Physics::update() : Time step bullet as often as necessary. This is
using the steering etc information set above.
ProtocolManager::update() : Synchronous protocol updates.
World::updateTime() : Increase time from T to T+DT.

View File

@ -48,9 +48,9 @@ CIrrDeviceStub::CIrrDeviceStub(const SIrrlichtCreationParameters& params)
}
else
FileSystem = io::createFileSystem();
core::stringc s = "Irrlicht Engine version ";
s.append(getVersion());
os::Printer::log(s.c_str(), ELL_INFORMATION);
//core::stringc s = "Irrlicht Engine version ";
//s.append(getVersion());
//os::Printer::log(s.c_str(), ELL_INFORMATION);
checkVersion(params.SDK_version_do_not_use);
}

View File

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

View File

@ -117,6 +117,15 @@ void STKConfig::load(const std::string &filename)
Log::fatal("StkConfig", "Wrong number of item switches defined in stk_config");
}
if (m_positional_smoothing.size() == 0)
{
Log::fatal("StkConfig", "No positional smoothing defined in stk_config.");
}
if (m_rotational_smoothing.size() == 0)
{
Log::fatal("StkConfig", "No rotationalsmoothing defined in stk_config.");
}
CHECK_NEG(m_max_karts, "<karts max=..." );
CHECK_NEG(m_item_switch_time, "item-switch-time" );
CHECK_NEG(m_bubblegum_counter, "bubblegum disappear counter");
@ -139,6 +148,8 @@ void STKConfig::load(const std::string &filename)
CHECK_NEG(m_replay_dt, "replay delta-t" );
CHECK_NEG(m_smooth_angle_limit, "physics smooth-angle-limit" );
CHECK_NEG(m_default_track_friction, "physics default-track-friction");
CHECK_NEG(m_physics_fps, "physics fps" );
CHECK_NEG(m_network_state_frequeny, "network state-frequency" );
CHECK_NEG(m_default_moveable_friction, "physics default-moveable-friction");
// Square distance to make distance checks cheaper (no sqrt)
@ -160,6 +171,7 @@ void STKConfig::init_defaults()
m_smooth_angle_limit = m_penalty_time =
m_default_track_friction = m_default_moveable_friction =
UNDEFINED;
m_physics_fps = -100;
m_bubblegum_counter = -100;
m_shield_restrict_weapos = false;
m_max_karts = -100;
@ -173,8 +185,8 @@ void STKConfig::init_defaults()
m_replay_delta_angle = -100;
m_replay_delta_pos2 = -100;
m_replay_dt = -100;
m_network_state_frequeny = -100;
m_title_music = NULL;
m_enable_networking = true;
m_smooth_normals = false;
m_same_powerup_mode = POWERUP_MODE_ONLY_IF_SAME;
m_ai_acceleration = 1.0f;
@ -249,6 +261,7 @@ void STKConfig::getAllData(const XMLNode * root)
physics_node->get("default-track-friction", &m_default_track_friction);
physics_node->get("default-moveable-friction",
&m_default_moveable_friction);
physics_node->get("fps", &m_physics_fps );
}
if (const XMLNode *startup_node= root->getNode("startup"))
@ -353,8 +366,12 @@ void STKConfig::getAllData(const XMLNode * root)
ai_node->get("acceleration", &m_ai_acceleration);
}
if(const XMLNode *networking_node= root->getNode("networking"))
networking_node->get("enable", &m_enable_networking);
if (const XMLNode *networking_node = root->getNode("networking"))
{
networking_node->get("state-frequency", &m_network_state_frequeny);
networking_node->get("positional-smoothing", &m_positional_smoothing );
networking_node->get("rotational-smoothing", &m_rotational_smoothing );
}
if(const XMLNode *replay_node = root->getNode("replay"))
{

View File

@ -27,6 +27,7 @@
*/
#include "network/remote_kart_info.hpp"
#include "utils/interpolation_array.hpp"
#include "utils/no_copy.hpp"
#include "utils/constants.hpp"
@ -82,6 +83,17 @@ public:
int m_max_karts; /**<Maximum number of karts. */
bool m_smooth_normals; /**< If normals for raycasts for wheels
should be interpolated. */
/** How many state updates per second the server will send. */
int m_network_state_frequeny;
/** Smoothing of prediction errors for position, defined as an
* InterpolationArray. */
InterpolationArray m_positional_smoothing;
/** Smoothing of prediction errors for rotations, defined as an
* InterpolationArray. */
InterpolationArray m_rotational_smoothing;
/** 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. */
@ -93,6 +105,9 @@ public:
/** Default friction to be used for any moveable, e.g. karts, balls. */
float m_default_moveable_friction;
/** Default FPS rate for physics. */
int m_physics_fps;
int m_max_skidmarks; /**<Maximum number of skid marks/kart. */
float m_skid_fadeout_time; /**<Time till skidmarks fade away. */
float m_near_ground; /**<Determines when a kart is not near
@ -105,7 +120,6 @@ public:
m_max_track_version; /**<version supported by this binary. */
int m_max_display_news; /**<How often a news message is displayed
before it is ignored. */
bool m_enable_networking;
/** Disable steering if skidding is stopped. This can help in making
* skidding more controllable (since otherwise when trying to steer while

View File

@ -205,7 +205,7 @@ public:
irr::core::stringc toString() const;
void revertToDefaults() { m_value = m_default_value; }
int getDefaultValue() { return m_default_value; }
operator int() const { return m_value; }
int& operator++(int dummy) { m_value++; return m_value; }
int& operator=(const int& v) { m_value = v; return m_value; }
@ -713,7 +713,7 @@ namespace UserConfigParams
// ---- Networking
PARAM_PREFIX IntUserConfigParam m_server_max_players
PARAM_DEFAULT( IntUserConfigParam(16, "server_max_players",
PARAM_DEFAULT( IntUserConfigParam(12, "server_max_players",
"Maximum number of players on the server.") );
PARAM_PREFIX StringListUserConfigParam m_stun_servers
@ -837,17 +837,6 @@ namespace UserConfigParams
PARAM_PREFIX BoolUserConfigParam m_crashed
PARAM_DEFAULT( BoolUserConfigParam(false, "crashed") );
#if defined(WIN32) && !defined(__CYGWIN__)
// No console on windows
# define CONSOLE_DEFAULT false
#else
# define CONSOLE_DEFAULT true
#endif
// No console on windows
PARAM_PREFIX BoolUserConfigParam m_log_errors_to_console
PARAM_DEFAULT( BoolUserConfigParam(
CONSOLE_DEFAULT, "log_errors", "Enable logging to console.") );
// ---- Camera
PARAM_PREFIX GroupUserConfigParam m_camera
PARAM_DEFAULT( GroupUserConfigParam("camera",

View File

@ -28,6 +28,7 @@
#include "graphics/stk_tex_manager.hpp"
#include "guiengine/engine.hpp"
#include "guiengine/skin.hpp"
#include "modes/profile_world.hpp"
#include "utils/string_utils.hpp"
#include <array>
@ -208,7 +209,7 @@ void FontWithFace::insertGlyph(wchar_t c, const GlyphInfo& gi)
const unsigned int cur_tex = m_spritebank->getTextureCount() -1;
#ifndef SERVER_ONLY
if (bits->buffer != NULL)
if (bits->buffer != NULL && !ProfileWorld::isNoGraphics())
{
video::ITexture* tex = m_spritebank->getTexture(cur_tex);
glBindTexture(GL_TEXTURE_2D, tex->getOpenGLTextureName());

View File

@ -79,7 +79,7 @@ STKTexture::STKTexture(video::IImage* img, const std::string& name)
STKTexture::~STKTexture()
{
#ifndef SERVER_ONLY
if (m_texture_name != 0)
if (m_texture_name != 0 && !ProfileWorld::isNoGraphics())
{
glDeleteTextures(1, &m_texture_name);
}

View File

@ -309,11 +309,12 @@ namespace GUIEngine
For icon buttons. A different icon to show when the item is focused.
\n
\subsection prop4 PROP_TEXT_ALIGN
<em> Name in XML files: </em> \c "text_align"
\subsection prop4 PROP_TEXT_ALIGN, PROP_TEXT_VALIGN
<em> Name in XML files: </em> \c "text_align", "text_valign"
used exclusively by label components. Value can be "right" or "center" (left
used if not specified).
used if not specified) for "text_align", or "top"/"center"/"bottom" for
valign.
\n
\subsection prop5 PROP_WORD_WRAP

View File

@ -210,6 +210,7 @@ if(prop_name != NULL) widget.m_properties[prop_flag] = core::stringc(prop_name).
READ_PROPERTY(icon, PROP_ICON);
READ_PROPERTY(focus_icon, PROP_FOCUS_ICON);
READ_PROPERTY(text_align, PROP_TEXT_ALIGN);
READ_PROPERTY(text_valign, PROP_TEXT_VALIGN);
READ_PROPERTY(min_value, PROP_MIN_VALUE);
READ_PROPERTY(max_value, PROP_MAX_VALUE);
READ_PROPERTY(square_items, PROP_SQUARE);

View File

@ -98,6 +98,7 @@ namespace GUIEngine
PROP_ICON,
PROP_FOCUS_ICON,
PROP_TEXT_ALIGN,
PROP_TEXT_VALIGN,
PROP_MIN_VALUE,
PROP_MAX_VALUE,
PROP_MAX_WIDTH,

View File

@ -76,7 +76,9 @@ void BubbleWidget::replaceText()
else if (m_properties[PROP_TEXT_ALIGN] == "right") align = EGUIA_LOWERRIGHT;
else if (translations->isRTLText(message)) align = EGUIA_LOWERRIGHT;
EGUI_ALIGNMENT valign = EGUIA_CENTER ; //TODO: make label v-align configurable through XML file?
EGUI_ALIGNMENT valign = EGUIA_CENTER;
if (m_properties[PROP_TEXT_VALIGN] == "top") valign = EGUIA_UPPERLEFT;
if (m_properties[PROP_TEXT_VALIGN] == "bottom") valign = EGUIA_LOWERRIGHT;
// find expanded bubble size
int text_height = irrwidget->getTextHeight();

View File

@ -66,7 +66,10 @@ void LabelWidget::add()
EGUI_ALIGNMENT align = EGUIA_UPPERLEFT;
if (m_properties[PROP_TEXT_ALIGN] == "center") align = EGUIA_CENTER;
else if (m_properties[PROP_TEXT_ALIGN] == "right") align = EGUIA_LOWERRIGHT;
EGUI_ALIGNMENT valign = EGUIA_CENTER ; //TODO: make label v-align configurable through XML file?
EGUI_ALIGNMENT valign = EGUIA_CENTER ;
if (m_properties[PROP_TEXT_VALIGN] == "top") valign = EGUIA_UPPERLEFT;
if (m_properties[PROP_TEXT_VALIGN] == "bottom") valign = EGUIA_LOWERRIGHT;
IGUIStaticText* irrwidget;
if (m_scroll_speed != 0)

View File

@ -204,7 +204,7 @@ void Attachment::set(AttachmentType type, float time,
{
BareNetworkString *buffer = new BareNetworkString(2);
saveState(buffer);
rwm->addEvent(this, buffer);
rwm->addEvent(this, buffer, /*confirmed*/true);
}
#endif
} // set
@ -269,7 +269,13 @@ void Attachment::saveState(BareNetworkString *buffer) const
void Attachment::rewindTo(BareNetworkString *buffer)
{
uint8_t type = buffer->getUInt8();
AttachmentType new_type = AttachmentType(type & 0x7f); // mask out bit 7
// FIXME Sometimes type == 255 is returned, reason unknown
if (new_type > ATTACH_NOTHING)
{
return;
}
// If there is no attachment, clear the attachment if necessary and exit
if(new_type==ATTACH_NOTHING)

View File

@ -333,7 +333,7 @@ void ItemManager::checkItemHit(AbstractKart* kart)
if((*i)->hitKart(kart->getXYZ(), kart))
{
// if we're not playing online, pick the item.
if (!RaceEventManager::getInstance()->isRunning())
if (!NetworkConfig::get()->isNetworking())
collectedItem(*i, kart);
else if (NetworkConfig::get()->isServer())
{

View File

@ -20,6 +20,7 @@
#define HEADER_AI_BASE_CONTROLLER_HPP
#include "karts/controller/controller.hpp"
#include "utils/cpp2011.hpp"
class AIProperties;
class Track;
@ -71,6 +72,7 @@ protected:
/** This can be called to detect if the kart is stuck (i.e. repeatedly
* hitting part of the track). */
bool isStuck() const { return m_stuck; }
// ------------------------------------------------------------------------
void determineTurnRadius(const Vec3 &end, Vec3 *center,
float *radius) const;
virtual void update (float delta);
@ -83,21 +85,28 @@ public:
AIBaseController(AbstractKart *kart);
virtual ~AIBaseController() {};
virtual void reset();
virtual bool disableSlipstreamBonus() const;
virtual void crashed(const Material *m);
virtual bool disableSlipstreamBonus() const OVERRIDE;
virtual void crashed(const Material *m) OVERRIDE;
static void enableDebug() {m_ai_debug = true; }
static void setTestAI(int n) {m_test_ai = n; }
static int getTestAI() { return m_test_ai; }
virtual void crashed(const AbstractKart *k) {};
virtual void handleZipper(bool play_sound) {};
virtual void finishedRace(float time) {};
virtual void crashed(const AbstractKart *k) OVERRIDE {};
virtual void handleZipper(bool play_sound) OVERRIDE {};
virtual void finishedRace(float time) OVERRIDE {};
virtual void collectedItem(const Item &item, int add_info=-1,
float previous_energy=0) {};
virtual void setPosition(int p) {};
virtual bool isPlayerController() const { return false; }
virtual bool isLocalPlayerController() const { return false; }
virtual void action(PlayerAction action, int value) {};
float previous_energy=0) OVERRIDE {};
virtual void setPosition(int p) OVERRIDE {};
virtual bool isPlayerController() const OVERRIDE { return false; }
virtual bool isLocalPlayerController() const OVERRIDE { return false; }
virtual bool action(PlayerAction action, int value, bool dry_run=false) OVERRIDE
{
return true;
};
virtual void skidBonusTriggered() {};
// ------------------------------------------------------------------------
/** Not used for AIs. */
virtual void saveState(BareNetworkString *buffer) const OVERRIDE {}
virtual void rewindTo(BareNetworkString *buffer) OVERRIDE {}
}; // AIBaseController

View File

@ -21,6 +21,8 @@
#include <irrString.h>
using namespace irr;
class BareNetworkString;
/**
* \defgroup controller Karts/controller
* Contains kart controllers, which are either human players or AIs
@ -31,6 +33,7 @@ using namespace irr;
#include "states_screens/state_manager.hpp"
class AbstractKart;
class BareNetworString;
class Item;
class KartControl;
class Material;
@ -74,6 +77,9 @@ public:
* rubber-banding. */
virtual bool isPlayerController () const = 0;
virtual bool disableSlipstreamBonus() const = 0;
virtual void saveState(BareNetworkString *buffer) const = 0;
virtual void rewindTo(BareNetworkString *buffer) = 0;
// ---------------------------------------------------------------------------
/** Sets the controller name for this controller. */
virtual void setControllerName(const std::string &name)
@ -83,7 +89,7 @@ public:
const std::string &getControllerName() const { return m_controller_name; }
// ------------------------------------------------------------------------
/** Default: ignore actions. Only PlayerController get them. */
virtual void action(PlayerAction action, int value) = 0;
virtual bool action(PlayerAction action, int value, bool dry_run=false) = 0;
// ------------------------------------------------------------------------
/** Callback whenever a new lap is triggered. Used by the AI
* to trigger a recomputation of the way to use. */

View File

@ -82,9 +82,10 @@ void GhostController::addReplayTime(float time)
} // addReplayTime
//-----------------------------------------------------------------------------
void GhostController::action(PlayerAction action, int value)
bool GhostController::action(PlayerAction action, int value, bool dry_run)
{
// Watching replay use only
if (action == PA_LOOK_BACK)
m_controls->setLookBack(value!=0);
return true;
} // action

View File

@ -58,9 +58,13 @@ public:
virtual void setPosition(int p) OVERRIDE {}
virtual bool isPlayerController() const OVERRIDE { return false; }
virtual bool isLocalPlayerController() const OVERRIDE { return false; }
virtual void action(PlayerAction action, int value) OVERRIDE;
virtual bool action(PlayerAction action, int value,
bool dry_run=false) OVERRIDE;
virtual void skidBonusTriggered() OVERRIDE {}
virtual void newLap(int lap) OVERRIDE {}
virtual void saveState(BareNetworkString *buffer) const {};
virtual void rewindTo(BareNetworkString *buffer) {};
void addReplayTime(float time);
// ------------------------------------------------------------------------
bool isReplayEnd() const

View File

@ -18,6 +18,7 @@
#include "karts/controller/kart_control.hpp"
#include "network/protocols/game_protocol.hpp"
#include "network/rewind_manager.hpp"
@ -38,7 +39,7 @@ void KartControl::rewind(BareNetworkString *buffer)
if(buffer->getTotalSize()>1)
{
// Full state including accel and steering was saved
setFromBuffer(buffer);
rewindTo(buffer);
}
else // only a button event was stored
{
@ -46,140 +47,58 @@ void KartControl::rewind(BareNetworkString *buffer)
}
} // rewind
// ------------------------------------------------------------------------
/** Sets this KartControl form the given value (basically a copy). This
* function uses the explicit setSteer() etc function, which means that
* rewind information will be collected.
*/
void KartControl::set(const KartControl &c)
{
setAccel(c.getAccel());
setBrake(c.getBrake());
setFire(c.getFire());
setLookBack(c.getLookBack());
setNitro(c.getNitro());
setRescue(c.getRescue());
setSkidControl(c.getSkidControl());
setSteer(c.getSteer());
} // set
// ------------------------------------------------------------------------
/** Sets the current steering value. */
void KartControl::setSteer(float f)
{
float old_steer = m_steer;
m_steer = f;
if (RewindManager::isEnabled() && !RewindManager::get()->isRewinding() &&
old_steer != m_steer )
{
// Save full status
BareNetworkString *buffer = new BareNetworkString(getLength());
copyToBuffer(buffer);
RewindManager::get()->addEvent(this, buffer);
}
} // setSteer
// ----------------------------------------------------------------------------
/** Sets the acceleration. */
void KartControl::setAccel(float f)
{
float old_accel = m_accel;
m_accel = f;
if (RewindManager::isEnabled() && !RewindManager::get()->isRewinding() &&
old_accel != m_accel )
{
BareNetworkString *buffer = new BareNetworkString(getLength());
copyToBuffer(buffer);
RewindManager::get()->addEvent(this, buffer);
}
} // setAccel
// ----------------------------------------------------------------------------
/** Sets if the kart is braking. */
void KartControl::setBrake(bool b)
{
bool old_brake = m_brake;
m_brake = b;
if (RewindManager::isEnabled() && !RewindManager::get()->isRewinding() &&
old_brake != m_brake )
{
// Only store the buttons in this case
BareNetworkString *buffer = new BareNetworkString(1);
buffer->addUInt8(getButtonsCompressed());
RewindManager::get()->addEvent(this, buffer);
}
} // setBrake
// ----------------------------------------------------------------------------
/** Sets if the kart activates nitro. */
void KartControl::setNitro(bool b)
{
bool old_nitro = m_nitro;
m_nitro = b;
if (RewindManager::isEnabled() && !RewindManager::get()->isRewinding() &&
old_nitro != m_nitro )
{
BareNetworkString *buffer = new BareNetworkString(1);
buffer->addUInt8(getButtonsCompressed());
RewindManager::get()->addEvent(this, buffer);
}
} // setNitro
// ----------------------------------------------------------------------------
/** Sets the skid control for this kart. */
void KartControl::setSkidControl(SkidControl sc)
{
SkidControl old_skid = m_skid;
m_skid = sc;
if (RewindManager::isEnabled() && !RewindManager::get()->isRewinding() &&
old_skid != m_skid )
{
BareNetworkString *buffer = new BareNetworkString(1);
buffer->addUInt8(getButtonsCompressed());
RewindManager::get()->addEvent(this, buffer);
}
} // seSkidControl
// ----------------------------------------------------------------------------
/** Returns if this kart wants to get rescued. */
void KartControl::setRescue(bool b)
{
bool old_rescue = m_rescue;
m_rescue = b;
if (RewindManager::isEnabled() && !RewindManager::get()->isRewinding() &&
old_rescue != m_rescue)
{
BareNetworkString *buffer = new BareNetworkString(1);
buffer->addUInt8(getButtonsCompressed());
RewindManager::get()->addEvent(this, buffer);
}
} // setRescue
// ----------------------------------------------------------------------------
/** Sets if the kart wants to fire. */
void KartControl::setFire(bool b)
{
bool old_fire = m_fire;
m_fire = b;
if (RewindManager::isEnabled() && !RewindManager::get()->isRewinding() &&
old_fire != m_fire )
{
BareNetworkString *buffer = new BareNetworkString(1);
buffer->addUInt8(getButtonsCompressed());
RewindManager::get()->addEvent(this, buffer);
}
} // setFire
// ----------------------------------------------------------------------------
/** Sets if the kart wants to look (and therefore also fires) backwards. */
void KartControl::setLookBack(bool b)
{
bool old_look = m_look_back;
m_look_back = b;
if (RewindManager::isEnabled() && !RewindManager::get()->isRewinding() &&
old_look != m_look_back)
{
BareNetworkString *buffer = new BareNetworkString(1);
buffer->addUInt8(getButtonsCompressed());
RewindManager::get()->addEvent(this, buffer);
}
} // setLookBack

View File

@ -61,7 +61,6 @@ public:
void setRescue(bool b);
void setFire(bool b);
void setLookBack(bool b);
void set(const KartControl &c);
// ------------------------------------------------------------------------
KartControl()
@ -101,7 +100,7 @@ public:
static int getLength() { return 9; }
// ------------------------------------------------------------------------
/** Copies the important data from this objects into a memory buffer. */
void copyToBuffer(BareNetworkString *buffer) const
void saveState(BareNetworkString *buffer) const
{
buffer->add(m_steer);
buffer->add(m_accel);
@ -110,7 +109,7 @@ public:
// ------------------------------------------------------------------------
/** Restores this object from a previously saved memory buffer. */
void setFromBuffer(BareNetworkString *buffer)
void rewindTo(BareNetworkString *buffer)
{
m_steer = buffer->getFloat();
m_accel = buffer->getFloat();

View File

@ -38,7 +38,8 @@
#include "karts/rescue_animation.hpp"
#include "modes/world.hpp"
#include "network/network_config.hpp"
#include "network/race_event_manager.hpp"
#include "network/protocols/game_protocol.hpp"
#include "network/rewind_manager.hpp"
#include "race/history.hpp"
#include "states_screens/race_gui_base.hpp"
#include "tracks/track.hpp"
@ -138,19 +139,34 @@ void LocalPlayerController::resetInputState()
* if between 1 and 32767, it indicates an analog value,
* and if it's 0 it indicates that the corresponding button
* was released.
* \param dry_run If set it will return if this action will trigger a
* state change or not.
* \return True if dry_run==true and a state change would be triggered.
* If dry_run==false, it returns true.
*/
void LocalPlayerController::action(PlayerAction action, int value)
bool LocalPlayerController::action(PlayerAction action, int value,
bool dry_run)
{
PlayerController::action(action, value);
// If this event does not change the control state (e.g.
// it's a (auto) repeat event), do nothing. This especially
// optimises traffic to the server and other clients.
if (!PlayerController::action(action, value, /*dry_run*/true)) return false;
// If this is a client, send the action to the server
if (World::getWorld()->isNetworkWorld() &&
NetworkConfig::get()->isClient() &&
RaceEventManager::getInstance()->isRunning() )
// Register event with history
if(!history->replayHistory())
history->addEvent(m_kart->getWorldKartId(), action, value);
// If this is a client, send the action to networking layer
if (World::getWorld()->isNetworkWorld() &&
NetworkConfig::get()->isClient() &&
!RewindManager::get()->isRewinding() )
{
RaceEventManager::getInstance()->controllerAction(this, action, value);
GameProtocol::lock()
->controllerAction(m_kart->getWorldKartId(),
action, value,
m_steer_val_l, m_steer_val_r);
}
return PlayerController::action(action, value, /*dry_run*/false);
} // action
//-----------------------------------------------------------------------------
@ -160,7 +176,6 @@ void LocalPlayerController::steer(float dt, int steer_val)
{
if(UserConfigParams::m_gamepad_debug)
{
Log::debug("LocalPlayerController", "steering: steer_val %d ", steer_val);
RaceGUIBase* gui_base = World::getWorld()->getRaceGUI();
gui_base->clearAllMessages();
gui_base->addMessage(StringUtils::insertValues(L"steer_val %i", steer_val),

View File

@ -61,7 +61,8 @@ public:
const int local_playerID);
~LocalPlayerController();
void update (float) OVERRIDE;
void action (PlayerAction action, int value) OVERRIDE;
bool action (PlayerAction action, int value,
bool dry_run=false) OVERRIDE;
virtual void handleZipper (bool play_sound) OVERRIDE;
void collectedItem (const Item &item, int add_info=-1,
float previous_energy=0) OVERRIDE;

View File

@ -85,98 +85,144 @@ void PlayerController::resetInputState()
* releasing right, the steering must switch to left again. Similarly it
* handles 'press left, press right, release left' (in which case still
* right must be selected). Similarly for braking and acceleration.
* \param action The action to be executed.
* \param value If 32768, it indicates a digital value of 'fully set'
* if between 1 and 32767, it indicates an analog value,
* and if it's 0 it indicates that the corresponding button
* was released.
* This function can be run in two modes: first, if 'dry_run' is set,
* it will return true if this action will cause a state change. This
* is sued in networking to avoid sending events to the server (and then
* to other clients) if they are just (e.g. auto) repeated events/
* \param action The action to be executed.
* \param value If 32768, it indicates a digital value of 'fully set'
* if between 1 and 32767, it indicates an analog value,
* and if it's 0 it indicates that the corresponding button
* was released.
* \param dry_run If set, it will only test if the parameter will trigger
* a state change. If not set, the appropriate actions
* (i.e. input state change) will be done.
* \return If dry_run is set, will return true if this action will
* cause a state change. If dry_run is not set, will return
* false.
*/
void PlayerController::action(PlayerAction action, int value)
bool PlayerController::action(PlayerAction action, int value, bool dry_run)
{
/** If dry_run (parameter) is true, this macro tests if this action would
* trigger a state change in the specified variable (without actually
* doing it). If it will trigger a state change, the marco will trigger
* immediatley a return to the caller. If dry_run is false, it will only
* assign the new value to the variable (and not return to the user
* early). The do-while(0) helps using this macro e.g. in the 'then'
* clause of an if statement. */
#define SET_OR_TEST(var, value) \
do \
{ \
if(dry_run) \
{ \
if (var != (value) ) return true; \
} \
else \
{ \
var = value; \
} \
} while(0)
/** Basically the same as the above macro, but is uses getter/setter
* funcitons. The name of the setter/getter is set'name'(value) and
* get'name'(). */
#define SET_OR_TEST_GETTER(name, value) \
do \
{ \
if(dry_run) \
{ \
if (m_controls->get##name() != (value) ) return true; \
} \
else \
{ \
m_controls->set##name(value); \
} \
} while(0)
switch (action)
{
case PA_STEER_LEFT:
m_steer_val_l = value;
SET_OR_TEST(m_steer_val_l, value);
if (value)
{
m_steer_val = value;
if(m_controls->getSkidControl()==KartControl::SC_NO_DIRECTION)
m_controls->setSkidControl(KartControl::SC_LEFT);
SET_OR_TEST(m_steer_val, value);
if (m_controls->getSkidControl() == KartControl::SC_NO_DIRECTION)
SET_OR_TEST_GETTER(SkidControl, KartControl::SC_LEFT);
}
else
m_steer_val = m_steer_val_r;
SET_OR_TEST(m_steer_val, m_steer_val_r);
break;
case PA_STEER_RIGHT:
m_steer_val_r = -value;
SET_OR_TEST(m_steer_val_r, -value);
if (value)
{
m_steer_val = -value;
if(m_controls->getSkidControl()==KartControl::SC_NO_DIRECTION)
m_controls->setSkidControl(KartControl::SC_RIGHT);
SET_OR_TEST(m_steer_val, -value);
if (m_controls->getSkidControl() == KartControl::SC_NO_DIRECTION)
SET_OR_TEST_GETTER(SkidControl, KartControl::SC_RIGHT);
}
else
m_steer_val = m_steer_val_l;
SET_OR_TEST(m_steer_val, m_steer_val_l);
break;
case PA_ACCEL:
m_prev_accel = value;
SET_OR_TEST(m_prev_accel, value);
if (value && !(m_penalty_time > 0.0f))
{
m_controls->setAccel(value/32768.0f);
m_controls->setBrake(false);
m_controls->setNitro(m_prev_nitro);
SET_OR_TEST_GETTER(Accel, value/32768.0f);
SET_OR_TEST_GETTER(Brake, false);
SET_OR_TEST_GETTER(Nitro, m_prev_nitro);
}
else
{
m_controls->setAccel(0.0f);
m_controls->setBrake(m_prev_brake);
m_controls->setNitro(false);
SET_OR_TEST_GETTER(Accel, 0.0f);
SET_OR_TEST_GETTER(Brake, m_prev_brake);
SET_OR_TEST_GETTER(Nitro, false);
}
break;
case PA_BRAKE:
m_prev_brake = value!=0;
SET_OR_TEST(m_prev_brake, value!=0);
// let's consider below that to be a deadzone
if(value > 32768/2)
{
m_controls->setBrake(true);
m_controls->setAccel(0.0f);
m_controls->setNitro(false);
SET_OR_TEST_GETTER(Brake, true);
SET_OR_TEST_GETTER(Accel, 0.0f);
SET_OR_TEST_GETTER(Nitro, false);
}
else
{
m_controls->setBrake(false);
m_controls->setAccel(m_prev_accel/32768.0f);
SET_OR_TEST_GETTER(Brake, false);
SET_OR_TEST_GETTER(Accel, m_prev_accel/32768.0f);
// Nitro still depends on whether we're accelerating
m_controls->setNitro(m_prev_nitro && m_prev_accel);
SET_OR_TEST_GETTER(Nitro, m_prev_nitro && m_prev_accel);
}
break;
case PA_NITRO:
// This basically keeps track whether the button still is being pressed
m_prev_nitro = (value != 0);
SET_OR_TEST(m_prev_nitro, value != 0 );
// Enable nitro only when also accelerating
m_controls->setNitro( ((value!=0) && m_controls->getAccel()) );
SET_OR_TEST_GETTER(Nitro, ((value!=0) && m_controls->getAccel()) );
break;
case PA_RESCUE:
m_controls->setRescue(value!=0);
SET_OR_TEST_GETTER(Rescue, value!=0);
break;
case PA_FIRE:
m_controls->setFire(value!=0);
SET_OR_TEST_GETTER(Fire, value!=0);
break;
case PA_LOOK_BACK:
m_controls->setLookBack(value!=0);
SET_OR_TEST_GETTER(LookBack, value!=0);
break;
case PA_DRIFT:
if(value==0)
m_controls->setSkidControl(KartControl::SC_NONE);
if (value == 0)
SET_OR_TEST_GETTER(SkidControl, KartControl::SC_NONE);
else
{
if(m_steer_val==0)
m_controls->setSkidControl(KartControl::SC_NO_DIRECTION);
if (m_steer_val == 0)
SET_OR_TEST_GETTER(SkidControl, KartControl::SC_NO_DIRECTION);
else
m_controls->setSkidControl(m_steer_val<0
? KartControl::SC_RIGHT
: KartControl::SC_LEFT );
SET_OR_TEST_GETTER(SkidControl, m_steer_val<0
? KartControl::SC_RIGHT
: KartControl::SC_LEFT );
}
break;
case PA_PAUSE_RACE:
@ -185,9 +231,21 @@ void PlayerController::action(PlayerAction action, int value)
default:
break;
}
if (dry_run) return false;
return true;
#undef SET_OR_TEST
#undef SET_OR_TEST_GETTER
} // action
//-----------------------------------------------------------------------------
void PlayerController::actionFromNetwork(PlayerAction p_action, int value,
int value_l, int value_r)
{
m_steer_val_l = value_l;
m_steer_val_r = value_r;
action(p_action, value);
} // actionFromNetwork
//-----------------------------------------------------------------------------
/** Handles steering for a player kart.
*/
@ -239,7 +297,6 @@ void PlayerController::steer(float dt, int steer_val)
if(steer>0.0f) steer=0.0f;
} // if steer<=0.0f
} // no key is pressed
m_controls->setSteer(std::min(1.0f, std::max(-1.0f, steer)) );
} // steer
@ -261,7 +318,7 @@ void PlayerController::update(float dt)
// Don't do steering if it's replay. In position only replay it doesn't
// matter, but if it's physics replay the gradual steering causes
// incorrect results, since the stored values are already adjusted.
if (!history->replayHistory())
if (!history->replayHistory() || !history->dontDoPhysics())
steer(dt, m_steer_val);
if (World::getWorld()->getPhase() == World::GOAL_PHASE)
@ -316,3 +373,18 @@ void PlayerController::handleZipper(bool play_sound)
{
m_kart->showZipperFire();
} // handleZipper
//-----------------------------------------------------------------------------
void PlayerController::saveState(BareNetworkString *buffer) const
{
buffer->addUInt32(m_steer_val).addUInt32(m_steer_val_l)
.addUInt32(m_steer_val_r);
} // copyToBuffer
//-----------------------------------------------------------------------------
void PlayerController::rewindTo(BareNetworkString *buffer)
{
m_steer_val = buffer->getUInt32();
m_steer_val_l = buffer->getUInt32();
m_steer_val_r = buffer->getUInt32();
} // rewindTo

View File

@ -44,11 +44,16 @@ public:
PlayerController(AbstractKart *kart);
virtual ~PlayerController ();
virtual void update (float) OVERRIDE;
virtual void action (PlayerAction action, int value) OVERRIDE;
virtual bool action (PlayerAction action, int value,
bool dry_run = false ) OVERRIDE;
virtual void actionFromNetwork(PlayerAction action, int value,
int value_l, int value_r);
virtual void skidBonusTriggered() OVERRIDE;
virtual void reset () OVERRIDE;
virtual void handleZipper(bool play_sound) OVERRIDE;
virtual void resetInputState();
virtual void saveState(BareNetworkString *buffer) const OVERRIDE;
virtual void rewindTo(BareNetworkString *buffer) OVERRIDE;
// ------------------------------------------------------------------------
virtual void collectedItem(const Item &item, int add_info=-1,
float previous_energy=0 ) OVERRIDE

View File

@ -60,6 +60,7 @@
#include "karts/max_speed.hpp"
#include "karts/rescue_animation.hpp"
#include "karts/skidding.hpp"
#include "main_loop.hpp"
#include "modes/overworld.hpp"
#include "modes/soccer_world.hpp"
#include "modes/world.hpp"
@ -68,7 +69,6 @@
#include "modes/soccer_world.hpp"
#include "modes/world.hpp"
#include "network/network_config.hpp"
#include "network/race_event_manager.hpp"
#include "network/rewind_manager.hpp"
#include "physics/btKart.hpp"
#include "physics/btKartRaycast.hpp"
@ -1266,7 +1266,7 @@ void Kart::update(float dt)
// is used furthermore for engine power, camera distance etc
updateSpeed();
if(!history->replayHistory() && !RewindManager::get()->isRewinding())
if(!history->replayHistory() || !history->dontDoPhysics())
m_controller->update(dt);
#undef DEBUG_CAMERA_SHAKE
@ -1285,18 +1285,17 @@ void Kart::update(float dt)
#ifdef DEBUG_TO_COMPARE_KART_PHYSICS
// This information is useful when comparing kart physics, e.g. to
// see top speed, acceleration (i.e. time to top speed) etc.
Log::verbose("physics", "%s t %f %f xyz %f %f %f v %f %f %f sk %f %d %f %f %f st %f %f",
Log::verbose("physics", " %s t %f %f xyz(9-11) %f %f %f v(13-15) %f %f %f steerf(17) %f maxangle(19) %f speed(21) %f steering(23-24) %f %f clock %lf",
getIdent().c_str(),
World::getWorld()->getTime(), dt,
getXYZ().getX(), getXYZ().getY(), getXYZ().getZ(),
getVelocity().getX(), getVelocity().getY(), getVelocity().getZ(),
m_skidding->getSkidFactor(),
m_skidding->getSkidState(),
m_skidding->getSteeringFraction(),
getMaxSteerAngle(),
m_speed,
m_vehicle->getWheelInfo(0).m_steering,
m_vehicle->getWheelInfo(1).m_steering
getVelocity().getX(), getVelocity().getY(), getVelocity().getZ(), //13,14,15
m_skidding->getSteeringFraction(), //19
getMaxSteerAngle(), //20
m_speed, //21
m_vehicle->getWheelInfo(0).m_steering, //23
m_vehicle->getWheelInfo(1).m_steering, //24
StkTime::getRealTime()
);
#endif
@ -1417,7 +1416,25 @@ void Kart::update(float dt)
old_group = m_body->getBroadphaseHandle()->m_collisionFilterGroup;
m_body->getBroadphaseHandle()->m_collisionFilterGroup = 0;
}
#ifdef XX
Log::verbose("physicsafter", "%s t %f %f xyz(9-11) %f %f %f %f %f %f "
"v(16-18) %f %f %f steerf(20) %f maxangle(22) %f speed(24) %f "
"steering(26-27) %f %f clock(29) %lf",
getIdent().c_str(),
World::getWorld()->getTime(), dt,
getXYZ().getX(), getXYZ().getY(), getXYZ().getZ(),
m_body->getWorldTransform().getOrigin().getX(),
m_body->getWorldTransform().getOrigin().getY(),
m_body->getWorldTransform().getOrigin().getZ(),
getVelocity().getX(), getVelocity().getY(), getVelocity().getZ(), //13,14,15
m_skidding->getSteeringFraction(), //19
getMaxSteerAngle(), //20
m_speed, //21
m_vehicle->getWheelInfo(0).m_steering, //23
m_vehicle->getWheelInfo(1).m_steering, //24
StkTime::getRealTime()
);
#endif
// After the physics step was done, the position of the wheels (as stored
// in wheelInfo) is actually outdated, since the chassis was moved
// according to the force acting from the wheels. So the center of the
@ -1521,8 +1538,8 @@ void Kart::update(float dt)
// Check if any item was hit.
// check it if we're not in a network world, or if we're on the server
// (when network mode is on)
if (!RaceEventManager::getInstance()->isRunning() ||
NetworkConfig::get()->isServer())
if(!NetworkConfig::get()->isNetworking() ||
NetworkConfig::get()->isServer() )
ItemManager::get()->checkItemHit(this);
static video::SColor pink(255, 255, 133, 253);
@ -1755,7 +1772,7 @@ void Kart::handleMaterialSFX(const Material *material)
// terrain sound is not necessarily a looping sound so check its status before
// setting its speed, to avoid 'ressuscitating' sounds that had already stopped
if(m_terrain_sound &&
if(m_terrain_sound && main_loop->isLstSubstep() &&
(m_terrain_sound->getStatus()==SFXBase::SFX_PLAYING ||
m_terrain_sound->getStatus()==SFXBase::SFX_PAUSED))
{
@ -2387,10 +2404,13 @@ void Kart::updatePhysics(float dt)
*/
void Kart::updateEngineSFX(float dt)
{
// when going faster, use higher pitch for engine
if(!m_engine_sound || !SFXManager::get()->sfxAllowed())
// Only update SFX during the last substep (otherwise too many SFX commands
// in one frame), and if sfx are enabled
if(!m_engine_sound || !SFXManager::get()->sfxAllowed() ||
!main_loop->isLstSubstep() )
return;
// when going faster, use higher pitch for engine
if(isOnGround())
{
float max_speed = m_kart_properties->getEngineMaxSpeed();

View File

@ -21,6 +21,7 @@
#include "items/attachment.hpp"
#include "items/powerup.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/controller.hpp"
#include "karts/max_speed.hpp"
#include "karts/skidding.hpp"
#include "modes/world.hpp"
@ -50,6 +51,26 @@ void KartRewinder::reset()
Rewinder::reset();
} // reset
// ----------------------------------------------------------------------------
/** This function is called immediately before a rewind is done and saves
* the current transform for the kart. The difference between this saved
* transform and the new transform after rewind is the error that needs
* (smoothly) be applied to the graphical position of the kart.
*/
void KartRewinder::saveTransform()
{
m_saved_transform = getTrans();
} // saveTransform
// ----------------------------------------------------------------------------
void KartRewinder::computeError()
{
//btTransform error = getTrans() - m_saved_transform;
Vec3 pos_error = getTrans().getOrigin() - m_saved_transform.getOrigin();
btQuaternion rot_error(0, 0, 0, 1);
Kart::addError(pos_error, rot_error);
} // computeError
// ----------------------------------------------------------------------------
/** 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
@ -78,7 +99,8 @@ BareNetworkString* KartRewinder::saveState() const
// 2) Steering and other player controls
// -------------------------------------
getControls().copyToBuffer(buffer);
getControls().saveState(buffer);
getController()->saveState(buffer);
// 3) Attachment
// -------------
@ -112,6 +134,15 @@ void KartRewinder::rewindToState(BareNetworkString *buffer)
t.setRotation(buffer->getQuat());
btRigidBody *body = getBody();
body->setLinearVelocity(buffer->getVec3());
Log::info("KartRewinder", "t %f xyz %f %f %f v %f %f %f",
World::getWorld()->getTime(),
t.getOrigin().getX(),
t.getOrigin().getY(),
t.getOrigin().getZ(),
body->getLinearVelocity().getX(),
body->getLinearVelocity().getY(),
body->getLinearVelocity().getZ());
body->setAngularVelocity(buffer->getVec3());
// This function also reads the velocity, so it must be called
// after the velocities are set
@ -124,7 +155,8 @@ void KartRewinder::rewindToState(BareNetworkString *buffer)
// 2) Steering and other controls
// ------------------------------
getControls().setFromBuffer(buffer);
getControls().rewindTo(buffer);
getController()->rewindTo(buffer);
// 3) Attachment
// -------------

View File

@ -33,6 +33,9 @@ private:
// Flags to indicate the different event types
enum { EVENT_CONTROL = 0x01,
EVENT_ATTACH = 0x02 };
/** The transform of the kart before a rewind starts. */
btTransform m_saved_transform;
public:
KartRewinder(const std::string& ident,
unsigned int world_kart_id,
@ -40,6 +43,8 @@ public:
PerPlayerDifficulty difficulty,
std::shared_ptr<RenderInfo> ri);
virtual ~KartRewinder() {};
virtual void saveTransform() OVERRIDE;
virtual void computeError() OVERRIDE;
virtual BareNetworkString* saveState() const;
void reset();
virtual void rewindToState(BareNetworkString *p) OVERRIDE;

View File

@ -24,6 +24,9 @@
#include "graphics/irr_driver.hpp"
#include "graphics/material.hpp"
#include "graphics/material_manager.hpp"
#include "modes/world.hpp"
#include "network/network_config.hpp"
#include "network/rewind_manager.hpp"
#include "tracks/track.hpp"
#include "ISceneNode.h"
@ -35,6 +38,8 @@ Moveable::Moveable()
m_mesh = NULL;
m_node = NULL;
m_heading = 0;
m_positional_error = Vec3(0.0f, 0.0f, 0.0f);
m_rotational_error = btQuaternion(0.0f, 0.0f, 0.0f, 1.0f);
} // Moveable
//-----------------------------------------------------------------------------
@ -56,6 +61,24 @@ void Moveable::setNode(scene::ISceneNode *n)
m_node = n;
} // setNode
//-----------------------------------------------------------------------------
/** Adds a new error between graphical and physical position/rotation. Called
* in case of a rewind to allow to for smoothing the visuals in case of
* incorrect client prediction.
* \param pos_error Positional error to add.
* \param rot_Error Rotational error to add.
*/
void Moveable::addError(const Vec3& pos_error,
const btQuaternion &rot_error)
{
m_positional_error += pos_error;
Log::info("VisualError", "time %f addError %f %f %f size %f",
World::getWorld()->getTime(),
m_positional_error.getX(), m_positional_error.getY(), m_positional_error.getZ(),
m_positional_error.length());
m_rotational_error *= rot_error;
} // addError
//-----------------------------------------------------------------------------
/** Updates the graphics model. Mainly set the graphical position to be the
* same as the physics position, but uses offsets to position and rotation
@ -66,8 +89,20 @@ void Moveable::setNode(scene::ISceneNode *n)
void Moveable::updateGraphics(float dt, const Vec3& offset_xyz,
const btQuaternion& rotation)
{
// If this is a client, don't smooth error during rewinds
if (World::getWorld()->isNetworkWorld() &&
NetworkConfig::get()->isClient() &&
!RewindManager::get()->isRewinding())
{
float error = m_positional_error.length();
m_positional_error *= stk_config->m_positional_smoothing.get(error);
Log::info("VisualError", "time %f reduceError %f %f %f size %f",
World::getWorld()->getTime(),
m_positional_error.getX(), m_positional_error.getY(), m_positional_error.getZ(),
m_positional_error.length());
}
#ifndef SERVER_ONLY
Vec3 xyz=getXYZ()+offset_xyz;
Vec3 xyz=getXYZ()+offset_xyz - m_positional_error;
m_node->setPosition(xyz.toIrrVector());
btQuaternion r_all = getRotation()*rotation;
if(btFuzzyZero(r_all.getX()) && btFuzzyZero(r_all.getY()-0.70710677f) &&

View File

@ -40,7 +40,8 @@ class Material;
class Moveable: public NoCopy
{
private:
btVector3 m_velocityLC; /**<Velocity in kart coordinates. */
Vec3 m_velocityLC; /**<Velocity in kart coordinates. */
/** The bullet transform of this rigid body. */
btTransform m_transform;
/** The 'real' heading between -180 to 180 degrees. */
float m_heading;
@ -49,6 +50,14 @@ private:
/** The roll between -180 and 180 degrees. */
float m_roll;
/** Client prediction in networked games might cause the visual
* and physical position to be different. For visual smoothing
* this variable accumulates the error and reduces it over time. */
Vec3 m_positional_error;
/** Similar to m_positional_error for rotation. */
btQuaternion m_rotational_error;
protected:
UserPointer m_user_pointer;
scene::IMesh *m_mesh;
@ -119,7 +128,8 @@ public:
&getTrans() const {return m_transform;}
void setTrans(const btTransform& t);
void updatePosition();
}
; // class Moveable
void addError(const Vec3& pos_error,
const btQuaternion &rot_error);
}; // class Moveable
#endif

View File

@ -157,6 +157,7 @@
# include <direct.h>
# endif
#else
# include <signal.h>
# include <unistd.h>
#endif
#include <stdexcept>
@ -212,9 +213,9 @@
#include "network/network_config.hpp"
#include "network/network_string.hpp"
#include "network/rewind_manager.hpp"
#include "network/rewind_queue.hpp"
#include "network/servers_manager.hpp"
#include "network/stk_host.hpp"
#include "network/protocols/get_public_address.hpp"
#include "online/profile_manager.hpp"
#include "online/request_manager.hpp"
#include "race/grand_prix_manager.hpp"
@ -540,8 +541,8 @@ void cmdLineHelp()
" and the music.\n"
" -t, --track=NAME Start track NAME.\n"
" --gp=NAME Start the specified Grand Prix.\n"
" --add-gp-dir=DIR Load Grand Prix files in DIR. Setting will be saved "
"in config.xml under additional_gp_directory. Use "
" --add-gp-dir=DIR Load Grand Prix files in DIR. Setting will be saved\n"
"in config.xml under additional_gp_directory. Use\n"
"--add-gp-dir=\"\" to unset.\n"
" --stk-config=FILE use ./data/FILE instead of "
"./data/stk_config.xml\n"
@ -567,7 +568,7 @@ void cmdLineHelp()
" --no-graphics Do not display the actual race.\n"
" --demo-mode=t Enables demo mode after t seconds of idle time in "
"main menu.\n"
" --demo-tracks=t1,t2 List of tracks to be used in demo mode. No"
" --demo-tracks=t1,t2 List of tracks to be used in demo mode. No\n"
" spaces are allowed in the track names.\n"
" --demo-laps=n Number of laps to use in a demo.\n"
" --demo-karts=n Number of karts to use in a demo.\n"
@ -578,20 +579,22 @@ void cmdLineHelp()
// " --test-ai=n Use the test-ai for every n-th AI kart.\n"
// " (so n=1 means all Ais will be the test ai)\n"
// "
" --server=name Start a server (not a playing client).\n"
" --network-console Enable network console.\n"
" --wan-server=name Start a Wan server (not a playing client).\n"
" --public-server Allow direct connection to the server (without stk server)\n"
" --lan-server=name Start a LAN server (not a playing client).\n"
" --server-password= Sets a password for a server (both client&server).\n"
" --connect-now=ip Connect to a server with IP known now (in format x.x.x.x:xxx(port)).\n"
" --connect-now=ip Connect to a server with IP known now\n"
" (in format x.x.x.x:xxx(port)), the port should be its\n"
" server discovery port.\n"
" --login=s Automatically log in (set the login).\n"
" --password=s Automatically log in (set the password).\n"
" --port=n Port number to use.\n"
" --my-address=1.1.1.1:1 Own IP address (can replace stun protocol)\n"
" --disable-lan Disable LAN detection (connect using WAN).\n"
" --auto-connect Automatically connect to fist server and start race\n"
" --max-players=n Maximum number of clients (server only).\n"
" --no-console Does not write messages in the console but to\n"
" --no-console-log Does not write messages in the console but to\n"
" stdout.log.\n"
" --console Write messages in the console and files\n"
" -h, --help Show this help.\n"
" --log=N Set the verbosity to a value between\n"
" 0 (Debug) and 5 (Only Fatal messages)\n"
@ -675,12 +678,8 @@ int handleCmdLineOutputModifier()
Log::disableColor();
Log::verbose("main", "Colours disabled.");
}
if(CommandLine::has("--console"))
UserConfigParams::m_log_errors_to_console=true;
if(CommandLine::has("--no-console"))
UserConfigParams::m_log_errors_to_console=false;
if(CommandLine::has("--no-console-log"))
Log::toggleConsoleLog(false);
return 0;
}
@ -710,7 +709,7 @@ int handleCmdLinePreliminary()
UserConfigParams::m_verbosity |= UserConfigParams::LOG_MISC;
if(CommandLine::has("--debug=all") )
UserConfigParams::m_verbosity |= UserConfigParams::LOG_ALL;
if(CommandLine::has("--online"))
//if(CommandLine::has("--online"))
MainMenuScreen::m_enable_online=true;
#if !(defined(SERVER_ONLY) || defined(ANDROID))
if(CommandLine::has("--apitrace"))
@ -733,7 +732,6 @@ int handleCmdLinePreliminary()
if(CommandLine::has("--no-graphics") || CommandLine::has("-l"))
{
ProfileWorld::disableGraphics();
UserConfigParams::m_log_errors_to_console=true;
}
if(CommandLine::has("--screensize", &s) || CommandLine::has("-s", &s))
@ -892,7 +890,6 @@ int handleCmdLine()
int n;
std::string s;
bool try_login = false;
irr::core::stringw login, password;
if (CommandLine::has("--unit-testing"))
@ -985,7 +982,84 @@ int handleCmdLine()
UserConfigParams::m_check_debug=true;
}
if (CommandLine::has( "--difficulty", &s))
{
int n = atoi(s.c_str());
if(n<0 || n>RaceManager::DIFFICULTY_LAST)
Log::warn("main", "Invalid difficulty '%s' - ignored.\n",
s.c_str());
else
race_manager->setDifficulty(RaceManager::Difficulty(n));
} // --mode
if (CommandLine::has("--type", &n))
{
switch (n)
{
// The order here makes server creation screen easier
case 0: race_manager->setMinorMode(RaceManager::MINOR_MODE_NORMAL_RACE);
break;
case 1: race_manager->setMinorMode(RaceManager::MINOR_MODE_TIME_TRIAL);
break;
case 2: race_manager->setMinorMode(RaceManager::MINOR_MODE_3_STRIKES);
break;
case 3: race_manager->setMinorMode(RaceManager::MINOR_MODE_SOCCER);
break;
case 4: race_manager->setMinorMode(RaceManager::MINOR_MODE_FOLLOW_LEADER);
break;
default:
Log::warn("main", "Invalid race type '%d' - ignored.", n);
}
} // --type
if (CommandLine::has("--login", &s))
login = s.c_str();
if (CommandLine::has("--password", &s))
password = s.c_str();
bool can_wan = false;
if (!login.empty() && !password.empty())
{
irr::core::stringw s;
Online::XMLRequest* request =
PlayerManager::requestSignIn(login, password);
while (PlayerManager::getCurrentOnlineState() != PlayerProfile::OS_SIGNED_IN)
{
Online::RequestManager::get()->update(0.0f);
StkTime::sleep(1);
}
Log::info("Main", "Logged in from command-line.");
can_wan = true;
delete request;
}
if (!can_wan && CommandLine::has("--login-id", &n) &&
CommandLine::has("--token", &s))
{
NetworkConfig::get()->setCurrentUserId(n);
NetworkConfig::get()->setCurrentUserToken(s);
can_wan = true;
}
// Networking command lines
if(CommandLine::has("--network-console"))
STKHost::m_enable_console = true;
if (CommandLine::has("--server-password", &s))
{
core::stringw pw = StringUtils::xmlDecode(s);
NetworkConfig::get()->setPassword(StringUtils::wideToUtf8(pw));
}
if (CommandLine::has("--server-id-file", &s))
{
NetworkConfig::get()->setServerIdFile(
file_manager->getUserConfigFile(s));
}
if(CommandLine::has("--max-players", &n))
UserConfigParams::m_server_max_players=n;
NetworkConfig::get()->
setMaxPlayers(UserConfigParams::m_server_max_players);
if (CommandLine::has("--port", &n))
@ -1002,59 +1076,68 @@ int handleCmdLine()
if (CommandLine::has("--connect-now", &s))
{
TransportAddress ip(s);
TransportAddress me(2130706433/*127.0.0.1*/,
NetworkConfig::get()->getServerDiscoveryPort() );
NetworkConfig::get()->setIsLAN();
if (ip.isLAN())
{
NetworkConfig::get()->setIsLAN();
}
else
{
NetworkConfig::get()->setIsWAN();
NetworkConfig::get()->setDirectConnect(true);
}
NetworkConfig::get()->setIsServer(false);
NetworkConfig::get()->setMyAddress(me);
Log::info("main", "Try to connect to server '%s'.",
ip.toString().c_str() );
irr::core::stringw name = StringUtils::utf8ToWide(ip.toString());
ServersManager::get()->addServer(new Server(name, /*lan*/true,
16, 0, ip));
ServersManager::get()->addServer(new Server(0, name,
NetworkConfig::get()->getMaxPlayers(), 0,
race_manager->getDifficulty(),
NetworkConfig::get()->getServerGameMode(race_manager->getMinorMode(),
race_manager->getMajorMode()), ip));
ServersManager::get()->setJoinedServer(0);
STKHost::create();
}
if(CommandLine::has("--server", &s))
if (CommandLine::has("--wan-server", &s))
{
NetworkConfig::get()->setServerName(core::stringw(s.c_str()));
NetworkConfig::get()->setIsServer(true);
NetworkConfig::get()->setIsWAN();
STKHost::create();
Log::info("main", "Creating a WAN server '%s'.", s.c_str());
// Try to use saved user token if exists
PlayerProfile* player = PlayerManager::getCurrentPlayer();
if (!can_wan && player && player->wasOnlineLastTime() &&
player->wasOnlineLastTime() && player->hasSavedSession())
{
while (PlayerManager::getCurrentOnlineState() != PlayerProfile::OS_SIGNED_IN)
{
Online::RequestManager::get()->update(0.0f);
StkTime::sleep(1);
}
can_wan = true;
}
else if (!can_wan)
{
Log::warn("main", "No saved online player session to create a wan server");
}
if (can_wan)
{
NetworkConfig::get()->setServerName(StringUtils::xmlDecode(s));
NetworkConfig::get()->setIsServer(true);
NetworkConfig::get()->setIsWAN();
STKHost::create();
Log::info("main", "Creating a WAN server '%s'.", s.c_str());
}
}
if (CommandLine::has("--lan-server", &s))
else if (CommandLine::has("--lan-server", &s))
{
NetworkConfig::get()->setServerName(core::stringw(s.c_str()));
NetworkConfig::get()->setServerName(StringUtils::xmlDecode(s));
NetworkConfig::get()->setIsServer(true);
NetworkConfig::get()->setIsLAN();
STKHost::create();
Log::info("main", "Creating a LAN server '%s'.", s.c_str());
}
if (CommandLine::has("--server-password", &s))
if (CommandLine::has("--auto-connect"))
{
NetworkConfig::get()->setPassword(s);
NetworkConfig::get()->setAutoConnect(true);
}
if(CommandLine::has("--max-players", &n))
UserConfigParams::m_server_max_players=n;
if(CommandLine::has("--start-console"))
STKHost::m_enable_console = true;
if(CommandLine::has("--login", &s) )
{
login = s.c_str();
try_login = true;
} // --login
if(CommandLine::has("--password", &s))
password = s.c_str();
if (CommandLine::has("--my-address", &s))
GetPublicAddress::setMyIPAddress(s);
/** Disable detection of LAN connection when connecting via WAN. This is
* mostly a debugging feature to force using WAN connection. */
if (CommandLine::has("--disable-lan"))
@ -1112,31 +1195,6 @@ int handleCmdLine()
race_manager->setNumKarts((int)l.size()+1);
} // --ai
if(CommandLine::has( "--mode", &s) || CommandLine::has( "--difficulty", &s))
{
int n = atoi(s.c_str());
if(n<0 || n>RaceManager::DIFFICULTY_LAST)
Log::warn("main", "Invalid difficulty '%s' - ignored.\n",
s.c_str());
else
race_manager->setDifficulty(RaceManager::Difficulty(n));
} // --mode
if(CommandLine::has("--type", &n))
{
switch (n)
{
case 0: race_manager->setMinorMode(RaceManager::MINOR_MODE_NORMAL_RACE);
break;
case 1: race_manager->setMinorMode(RaceManager::MINOR_MODE_TIME_TRIAL);
break;
case 2: race_manager->setMinorMode(RaceManager::MINOR_MODE_FOLLOW_LEADER);
break;
default:
Log::warn("main", "Invalid race type '%d' - ignored.", n);
}
} // --type
if(CommandLine::has("--track", &s) || CommandLine::has("-t", &s))
{
race_manager->setTrack(s);
@ -1270,7 +1328,8 @@ int handleCmdLine()
history->doReplayHistory( (History::HistoryReplayMode)n);
// Force the no-start screen flag, since this initialises
// the player structures correctly.
UserConfigParams::m_no_start_screen = true;
if(!MainMenuScreen::m_enable_online)
UserConfigParams::m_no_start_screen = true;
} // --history=%d
if(CommandLine::has("--history")) // handy default for --history=1
@ -1324,24 +1383,12 @@ int handleCmdLine()
CommandLine::reportInvalidParameters();
if(ProfileWorld::isProfileMode())
if (ProfileWorld::isProfileMode() || ProfileWorld::isNoGraphics())
{
UserConfigParams::m_sfx = false; // Disable sound effects
UserConfigParams::m_music = false;// and music when profiling
}
if (try_login)
{
irr::core::stringw s;
Online::XMLRequest* request =
PlayerManager::requestSignIn(login, password);
if (request->isSuccess())
{
Log::info("Main", "Logged in from command-line.");
}
}
return 1;
} // handleCmdLine
@ -1534,7 +1581,13 @@ int main(int argc, char *argv[] )
CommandLine::init(argc, argv);
CrashReporting::installHandlers();
#ifndef WIN32
signal(SIGTERM, [](int signum)
{
if (main_loop)
main_loop->abort();
});
#endif
srand(( unsigned ) time( 0 ));
try
@ -1565,7 +1618,11 @@ int main(int argc, char *argv[] )
// Get into menu mode initially.
input_manager->setMode(InputManager::MENU);
main_loop = new MainLoop();
int parent_pid;
if (CommandLine::has("--parent-process", &parent_pid))
main_loop = new MainLoop(parent_pid);
else
main_loop = new MainLoop(0/*parent_pid*/);
material_manager->loadMaterial();
// Preload the explosion effects (explode.png)
@ -1778,12 +1835,15 @@ int main(int argc, char *argv[] )
{
// This will setup the race manager etc.
history->Load();
race_manager->setupPlayerKartInfo();
race_manager->startNew(false);
main_loop->run();
// The run() function will only return if the user aborts.
Log::flushBuffers();
exit(-3);
if (!MainMenuScreen::m_enable_online)
{
race_manager->setupPlayerKartInfo();
race_manager->startNew(false);
main_loop->run();
// The run() function will only return if the user aborts.
Log::flushBuffers();
exit(-3);
} // if !online
}
// Not replaying
@ -1830,8 +1890,8 @@ int main(int argc, char *argv[] )
StateManager::get()->resetActivePlayers();
if(input_manager) delete input_manager; // if early crash avoid delete NULL
if(NetworkConfig::get()->isNetworking() && STKHost::existHost())
STKHost::get()->abort();
if (STKHost::existHost())
STKHost::get()->shutdown();
cleanSuperTuxKart();
@ -1940,12 +2000,7 @@ static void cleanSuperTuxKart()
// in the request manager, so it can not be deleted earlier.
if(addons_manager) delete addons_manager;
// FIXME: do we need to wait for threads there, can they be
// moved further up?
ServersManager::deallocate();
if(NetworkConfig::get()->isNetworking() && STKHost::existHost())
STKHost::destroy();
cleanUserConfig();
StateManager::deallocate();
@ -2026,6 +2081,9 @@ void runUnitTests()
Log::info("UnitTest", "Fonts for translation");
font_manager->unitTesting();
Log::info("UnitTest", "RewindQueue");
RewindQueue::unitTesting();
Log::info("UnitTest", "=====================");
Log::info("UnitTest", "Testing successful ");
Log::info("UnitTest", "=====================");

View File

@ -58,9 +58,6 @@ void override_default_params()
// Create default user istead of showing login screen to make life easier
UserConfigParams::m_enforce_current_player = true;
// Just for debugging
UserConfigParams::m_log_errors_to_console = true;
}
void android_main(struct android_app* app)

View File

@ -26,6 +26,7 @@
#include "graphics/irr_driver.hpp"
#include "graphics/material_manager.hpp"
#include "guiengine/engine.hpp"
#include "guiengine/message_queue.hpp"
#include "input/input_manager.hpp"
#include "input/wiimote_manager.hpp"
#include "modes/profile_world.hpp"
@ -33,21 +34,50 @@
#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"
#include "race/race_manager.hpp"
#include "states_screens/main_menu_screen.hpp"
#include "states_screens/online_screen.hpp"
#include "states_screens/state_manager.hpp"
#include "utils/profiler.hpp"
MainLoop* main_loop = 0;
MainLoop::MainLoop() :
m_abort(false)
MainLoop::MainLoop(unsigned parent_pid)
: m_abort(false), m_parent_pid(parent_pid)
{
m_curr_time = 0;
m_prev_time = 0;
m_throttle_fps = true;
m_curr_time = 0;
m_prev_time = 0;
m_throttle_fps = true;
m_is_last_substep = false;
#ifdef WIN32
if (parent_pid != 0)
{
std::string class_name = "separate_process";
class_name += StringUtils::toString(GetCurrentProcessId());
WNDCLASSEX wx = {};
wx.cbSize = sizeof(WNDCLASSEX);
wx.lpfnWndProc = [](HWND h, UINT m, WPARAM w, LPARAM l)->LRESULT
{
if (m == WM_DESTROY)
{
PostQuitMessage(0);
return 0;
}
return DefWindowProc(h, m, w, l);
};
wx.hInstance = GetModuleHandle(0);
wx.lpszClassName = &class_name[0];
if (RegisterClassEx(&wx))
{
CreateWindowEx(0, &class_name[0], "stk_server_only",
0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
}
}
#endif
} // MainLoop
//-----------------------------------------------------------------------------
@ -62,14 +92,8 @@ MainLoop::~MainLoop()
*/
float MainLoop::getLimitedDt()
{
m_prev_time = m_curr_time;
float dt = 0;
// If we are doing a replay, use the dt from the history file
if (World::getWorld() && history->replayHistory() )
{
dt = history->updateReplayAndGetDT();
return dt;
}
// In profile mode without graphics, run with a fixed dt of 1/60
if ((ProfileWorld::isProfileMode() && ProfileWorld::isNoGraphics()) ||
@ -79,12 +103,27 @@ float MainLoop::getLimitedDt()
}
IrrlichtDevice* device = irr_driver->getDevice();
m_prev_time = m_curr_time;
while( 1 )
{
m_curr_time = device->getTimer()->getRealTime();
dt = (float)(m_curr_time - m_prev_time);
// On a server (i.e. without graphics) the frame rate can be under
// 1 ms, i.e. dt = 0. Additionally, the resolution of a sleep
// statement is not that precise either: if the sleep statement
// would be consistent < 1ms, but the stk time would increase by
// 1 ms, the stk clock would be desynchronised from real time
// (it would go faster), resulting in synchronisation problems
// with clients (server time is supposed to be behind client time).
// So we play it safe by adding a loop to make sure at least 1ms
// (minimum time that can be handled by the integer timer) delay here.
while (dt <= 0)
{
StkTime::sleep(1);
m_curr_time = device->getTimer()->getRealTime();
dt = (float)(m_curr_time - m_prev_time);
}
const World* const world = World::getWorld();
if (UserConfigParams::m_fps_debug && world)
{
@ -110,33 +149,53 @@ float MainLoop::getLimitedDt()
// client and server will not be in synch anymore
if(!NetworkConfig::get()->isNetworking())
{
static const float max_elapsed_time = 3.0f*1.0f / 60.0f*1000.0f; /* time 3 internal substeps take */
if (dt > max_elapsed_time) dt = max_elapsed_time;
/* time 3 internal substeps take */
const float MAX_ELAPSED_TIME = 3.0f*1.0f / 60.0f*1000.0f;
if (dt > MAX_ELAPSED_TIME) dt = MAX_ELAPSED_TIME;
}
if (!m_throttle_fps || ProfileWorld::isProfileMode()) break;
// Throttle fps if more than maximum, which can reduce
// the noise the fan on a graphics card makes.
// When in menus, reduce FPS much, it's not necessary to push to the maximum for plain menus
// When in menus, reduce FPS much, it's not necessary to push to the
// maximum for plain menus
const int max_fps = (irr_driver->isRecording() &&
UserConfigParams::m_limit_game_fps ? UserConfigParams::m_record_fps :
StateManager::get()->throttleFPS() ? 60 : UserConfigParams::m_max_fps);
if (dt > 0)
{
const int current_fps = (int)(1000.0f / dt);
if (m_throttle_fps && current_fps > max_fps && !ProfileWorld::isProfileMode())
{
int wait_time = 1000 / max_fps - 1000 / current_fps;
if (wait_time < 1) wait_time = 1;
UserConfigParams::m_limit_game_fps )
? UserConfigParams::m_record_fps
: ( StateManager::get()->throttleFPS()
? 60
: UserConfigParams::m_max_fps );
const int current_fps = (int)(1000.0f / dt);
if (!m_throttle_fps || current_fps <= max_fps ||
ProfileWorld::isProfileMode() ) break;
int wait_time = 1000 / max_fps - 1000 / current_fps;
if (wait_time < 1) wait_time = 1;
PROFILER_PUSH_CPU_MARKER("Throttle framerate", 0, 0, 0);
StkTime::sleep(wait_time);
PROFILER_POP_CPU_MARKER();
} // while(1)
PROFILER_PUSH_CPU_MARKER("Throttle framerate", 0, 0, 0);
StkTime::sleep(wait_time);
PROFILER_POP_CPU_MARKER();
}
else break;
}
else break;
}
dt *= 0.001f;
// If this is a client, the server might request an
// adjustment of this client's world clock (to reduce
// number of rewinds).
if (World::getWorld() &&
NetworkConfig::get()->isClient() &&
!RewindManager::get()->isRewinding() )
{
dt = World::getWorld()->adjustDT(dt);
}
// If we are doing a replay, use the dt from the history file if this
// is not networked, otherwise history will use current time and dt
// to findout which events to replay
if (World::getWorld() && history->replayHistory() )
{
dt = history->updateReplayAndGetDT(World::getWorld()->getTime(), dt);
}
return dt;
} // getLimitedDt
@ -146,9 +205,12 @@ float MainLoop::getLimitedDt()
*/
void MainLoop::updateRace(float dt)
{
if (!World::getWorld()) return; // No race on atm - i.e. we are in menu
// The race event manager will update world in case of an online race
if (RaceEventManager::getInstance<RaceEventManager>()->isRunning())
RaceEventManager::getInstance<RaceEventManager>()->update(dt);
if ( RaceEventManager::getInstance() &&
RaceEventManager::getInstance()->isRunning() )
RaceEventManager::getInstance()->update(dt);
else
World::getWorld()->updateWorld(dt);
} // updateRace
@ -227,87 +289,157 @@ void MainLoop::run()
IrrlichtDevice* device = irr_driver->getDevice();
m_curr_time = device->getTimer()->getRealTime();
// DT keeps track of the leftover time, since the race update
// happens in fixed timesteps
float left_over_time = 0;
#ifdef WIN32
HANDLE parent = 0;
if (m_parent_pid != 0)
{
parent = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_parent_pid);
if (parent == 0 || parent == INVALID_HANDLE_VALUE)
{
Log::warn("MainLoop", "Cannot open parent handle, this child "
"may not be auto destroyed when parent is terminated");
}
}
#endif
while(!m_abort)
{
#ifdef WIN32
if (parent != 0 && parent != INVALID_HANDLE_VALUE)
{
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.message == WM_QUIT)
m_abort = true;
}
// If parent is killed, abort the child main loop too
if (WaitForSingleObject(parent, 0) != WAIT_TIMEOUT)
m_abort = true;
}
#endif
m_is_last_substep = false;
PROFILER_PUSH_CPU_MARKER("Main loop", 0xFF, 0x00, 0xF7);
m_prev_time = m_curr_time;
float dt = getLimitedDt();
left_over_time += getLimitedDt();
int num_steps = int(left_over_time * stk_config->m_physics_fps);
float dt = 1.0f / stk_config->m_physics_fps;
left_over_time -= num_steps * dt ;
if (!m_abort && !ProfileWorld::isNoGraphics())
if (STKHost::existHost() &&
STKHost::get()->requestedShutdown())
{
// Render the previous frame, and also handle all user input.
PROFILER_PUSH_CPU_MARKER("IrrDriver update", 0x00, 0x00, 0x7F);
irr_driver->update(dt);
SFXManager::get()->quickSound("anvil");
core::stringw msg = _("Connection to server is lost.");
if (!STKHost::get()->getErrorMessage().empty())
{
msg = STKHost::get()->getErrorMessage();
}
STKHost::get()->shutdown();
if (World::getWorld())
{
race_manager->exitRace();
}
if (!ProfileWorld::isNoGraphics())
{
GUIEngine::Screen* new_stack[] =
{
MainMenuScreen::getInstance(),
OnlineScreen::getInstance(), NULL
};
StateManager::get()->resetAndSetStack(new_stack);
MessageQueue::add(MessageQueue::MT_ERROR, msg);
}
NetworkConfig::get()->unsetNetworking();
}
// 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)
{
float frame_duration = num_steps * dt;
if (!ProfileWorld::isNoGraphics())
{
// Render the previous frame, and also handle all user input.
PROFILER_PUSH_CPU_MARKER("IrrDriver update", 0x00, 0x00, 0x7F);
irr_driver->update(frame_duration);
PROFILER_POP_CPU_MARKER();
PROFILER_PUSH_CPU_MARKER("Input/GUI", 0x7F, 0x00, 0x00);
#ifdef ENABLE_WIIUSE
wiimote_manager->update();
#endif
input_manager->update(frame_duration);
GUIEngine::update(frame_duration);
PROFILER_POP_CPU_MARKER();
PROFILER_PUSH_CPU_MARKER("Music", 0x7F, 0x00, 0x00);
SFXManager::get()->update();
PROFILER_POP_CPU_MARKER();
}
// Some protocols in network will use RequestManager
PROFILER_PUSH_CPU_MARKER("Database polling update", 0x00, 0x7F, 0x7F);
Online::RequestManager::get()->update(frame_duration);
PROFILER_POP_CPU_MARKER();
}
if (World::getWorld()) // race is active if world exists
for(int i=0; i<num_steps; i++)
{
PROFILER_PUSH_CPU_MARKER("Update race", 0, 255, 255);
updateRace(dt);
PROFILER_POP_CPU_MARKER();
} // if race is active
// We need to check again because update_race may have requested
// the main loop to abort; and it's not a good idea to continue
// since the GUI engine is no more to be called then.
// Also only do music, input, and graphics update if graphics are
// enabled.
if (!m_abort && !ProfileWorld::isNoGraphics())
{
PROFILER_PUSH_CPU_MARKER("Input/GUI", 0x7F, 0x00, 0x00);
input_manager->update(dt);
#ifdef ENABLE_WIIUSE
wiimote_manager->update();
#endif
GUIEngine::update(dt);
PROFILER_POP_CPU_MARKER();
// Update sfx and music after graphics, so that graphics code
// can use as many threads as possible without interfering
// with audio
PROFILER_PUSH_CPU_MARKER("Music", 0x7F, 0x00, 0x00);
SFXManager::get()->update();
PROFILER_POP_CPU_MARKER();
PROFILER_PUSH_CPU_MARKER("Protocol manager update", 0x7F, 0x00, 0x7F);
if (STKHost::existHost())
// Create the TimeStepInfo structure. For the first iteration
// this is done before the irr_driver update (since this needs
// to store input events at the event), but for any further
// substep another TimeStepInfo needs to be created here.
if (World::getWorld() && RewindManager::get()->isEnabled() && i>0)
{
if (STKHost::get()->requestedShutdown())
STKHost::get()->shutdown();
else
ProtocolManager::getInstance()->update(dt);
RewindManager::get()
->addNextTimeStep(World::getWorld()->getTime(), dt);
}
// Enable last substep in last iteration
m_is_last_substep = (i == num_steps - 1);
PROFILER_PUSH_CPU_MARKER("Update race", 0, 255, 255);
if (World::getWorld()) updateRace(dt);
PROFILER_POP_CPU_MARKER();
// We need to check again because update_race may have requested
// the main loop to abort; and it's not a good idea to continue
// since the GUI engine is no more to be called then.
if (m_abort) break;
PROFILER_PUSH_CPU_MARKER("Protocol manager update",
0x7F, 0x00, 0x7F);
if (auto pm = ProtocolManager::lock())
{
pm->update(dt);
}
PROFILER_POP_CPU_MARKER();
PROFILER_PUSH_CPU_MARKER("Database polling update", 0x00, 0x7F, 0x7F);
Online::RequestManager::get()->update(dt);
PROFILER_POP_CPU_MARKER();
}
else if (!m_abort && ProfileWorld::isNoGraphics())
{
PROFILER_PUSH_CPU_MARKER("Protocol manager update", 0x7F, 0x00, 0x7F);
if(NetworkConfig::get()->isNetworking())
ProtocolManager::getInstance()->update(dt);
PROFILER_POP_CPU_MARKER();
if (World::getWorld()) World::getWorld()->updateTime(dt);
} // for i < num_steps
PROFILER_PUSH_CPU_MARKER("Database polling update", 0x00, 0x7F, 0x7F);
Online::RequestManager::get()->update(dt);
PROFILER_POP_CPU_MARKER();
}
if (World::getWorld() )
{
World::getWorld()->updateTime(dt);
}
PROFILER_POP_CPU_MARKER();
m_is_last_substep = false;
PROFILER_POP_CPU_MARKER(); // MainLoop pop
PROFILER_SYNC_FRAME();
} // while !m_abort
#ifdef WIN32
if (parent != 0 && parent != INVALID_HANDLE_VALUE)
CloseHandle(parent);
#endif
} // run
//-----------------------------------------------------------------------------

View File

@ -21,7 +21,7 @@
#define HEADER_MAIN_LOOP_HPP
typedef unsigned long Uint32;
#include <atomic>
/** Management class for the whole gameflow, this is where the
main-loop is */
@ -29,17 +29,22 @@ class MainLoop
{
private:
/** True if the main loop should exit. */
bool m_abort;
std::atomic_bool m_abort;
/** True if the frame rate should be throttled. */
bool m_throttle_fps;
/** True during the last substep of the inner main loop (where world
* is updated). Used to reduce amount of updates (e.g. sfx positions
* etc). */
bool m_is_last_substep;
Uint32 m_curr_time;
Uint32 m_prev_time;
unsigned m_parent_pid;
float getLimitedDt();
void updateRace(float dt);
public:
MainLoop();
MainLoop(unsigned parent_pid);
~MainLoop();
void run();
void abort();
@ -47,6 +52,10 @@ public:
// ------------------------------------------------------------------------
/** Returns true if STK is to be stoppe. */
bool isAborted() const { return m_abort; }
// ------------------------------------------------------------------------
/** Returns if this is the last substep. Used to reduce the amount
* of updates (e.g. to sfx position) to once per rendered frame. */
bool isLstSubstep() const { return m_is_last_substep; }
}; // MainLoop
extern MainLoop* main_loop;

View File

@ -128,7 +128,6 @@ World::World() : WorldStatus()
m_schedule_pause = false;
m_schedule_unpause = false;
m_schedule_exit_race = false;
m_self_destruct = false;
m_schedule_tutorial = false;
m_is_network_world = false;
@ -230,14 +229,19 @@ void World::init()
{
Weather::getInstance<Weather>(); // create Weather instance
}
if((NetworkConfig::get()->isServer() && !ProfileWorld::isNoGraphics()) ||
race_manager->isWatchingReplay())
{
// In case that the server is running with gui or watching replay,
// create a camera and attach it to the first kart.
Camera::createCamera(World::getWorld()->getKart(0), 0);
}
if (Camera::getNumCameras() == 0)
{
if ( (NetworkConfig::get()->isServer() &&
!ProfileWorld::isNoGraphics() ) ||
race_manager->isWatchingReplay() )
{
// In case that the server is running with gui or watching replay,
// create a camera and attach it to the first kart.
Camera::createCamera(World::getWorld()->getKart(0), 0);
} // if server with graphics of is watching replay
} // if getNumCameras()==0
} // init
//-----------------------------------------------------------------------------
@ -850,18 +854,14 @@ void World::updateWorld(float dt)
m_schedule_unpause = false;
}
if (m_self_destruct)
{
delete this;
return;
}
// Don't update world if a menu is shown or the race is over.
if( getPhase() == FINISH_PHASE ||
getPhase() == IN_GAME_MENU_PHASE )
return;
if (!history->replayHistory())
if (!history->replayHistory() &&
! (NetworkConfig::get()->isClient() &&
RewindManager::get()->isRewinding() ) )
{
history->updateSaving(dt); // updating the saved state
}
@ -981,7 +981,9 @@ void World::update(float dt)
PROFILER_PUSH_CPU_MARKER("World::update (sub-updates)", 0x20, 0x7F, 0x00);
WorldStatus::update(dt);
RewindManager::get()->saveStates();
PROFILER_POP_CPU_MARKER();
PROFILER_PUSH_CPU_MARKER("World::update (RewindManager)", 0x20, 0x7F, 0x40);
RewindManager::get()->update(dt);
PROFILER_POP_CPU_MARKER();
PROFILER_PUSH_CPU_MARKER("World::update (Kart::upate)", 0x40, 0x7F, 0x00);
@ -1042,7 +1044,6 @@ void World::update(float dt)
void World::updateTime(const float dt)
{
WorldStatus::updateTime(dt);
RewindManager::get()->setCurrentTime(getTime(), dt);
} // updateTime
// ----------------------------------------------------------------------------
@ -1292,15 +1293,6 @@ void World::unpause()
}
} // pause
//-----------------------------------------------------------------------------
/** Call when the world needs to be deleted but you can't do it immediately
* because you are e.g. within World::update()
*/
void World::delayedSelfDestruct()
{
m_self_destruct = true;
} // delayedSelfDestruct
//-----------------------------------------------------------------------------
void World::escapePressed()
{

View File

@ -318,8 +318,6 @@ public:
* quadgraph. Override to change value. */
virtual bool useChecklineRequirements() const { return false; }
// ------------------------------------------------------------------------
void delayedSelfDestruct();
// ------------------------------------------------------------------------
virtual void escapePressed();
// ------------------------------------------------------------------------
virtual void loadCustomModels() {}

View File

@ -30,6 +30,7 @@
#include "network/network_config.hpp"
#include "network/protocols/client_lobby.hpp"
#include "network/protocols/server_lobby.hpp"
#include "network/rewind_manager.hpp"
#include "network/race_event_manager.hpp"
#include "tracks/track.hpp"
@ -61,6 +62,7 @@ WorldStatus::WorldStatus()
void WorldStatus::reset()
{
m_time = 0.0f;
m_adjust_time_by = 0.0f;
m_auxiliary_timer = 0.0f;
m_count_up_timer = 0.0f;
@ -206,6 +208,7 @@ void WorldStatus::updateTime(const float dt)
m_auxiliary_timer += dt;
if (UserConfigParams::m_artist_debug_mode &&
!NetworkConfig::get()->isNetworking() &&
race_manager->getNumberOfKarts() -
race_manager->getNumSpareTireKarts() == 1 &&
race_manager->getTrackName() != "tutorial")
@ -259,8 +262,7 @@ void WorldStatus::updateTime(const float dt)
if (!m_server_is_ready) return;
m_phase = READY_PHASE;
Protocol *p = LobbyProtocol::get();
ClientLobby *cl = dynamic_cast<ClientLobby*>(p);
auto cl = LobbyProtocol::get<ClientLobby>();
if (cl)
cl->startingRaceNow();
return; // Don't increase time
@ -282,7 +284,8 @@ void WorldStatus::updateTime(const float dt)
// In artist debug mode, when without opponents, skip the
// ready/set/go counter faster
if (UserConfigParams::m_artist_debug_mode &&
if (UserConfigParams::m_artist_debug_mode &&
!NetworkConfig::get()->isNetworking() &&
race_manager->getNumberOfKarts() -
race_manager->getNumSpareTireKarts() == 1 &&
race_manager->getTrackName() != "tutorial")
@ -390,7 +393,7 @@ void WorldStatus::updateTime(const float dt)
}
IrrlichtDevice *device = irr_driver->getDevice();
switch (m_clock_mode)
{
case CLOCK_CHRONO:
@ -426,6 +429,41 @@ void WorldStatus::updateTime(const float dt)
} // switch m_phase
} // update
//-----------------------------------------------------------------------------
/** If the server requests that a client's time should be adjusted,
* smoothly change the clock speed of this client to go a bit faster
* or slower till the overall adjustment was reached.
* \param dt Original time step size.
*/
float WorldStatus::adjustDT(float dt)
{
// If request, adjust world time to go ahead (adjust>0) or
// slow down (<0). This is done in 5% of dt steps so that the
// user will not notice this.
const float FRACTION = 0.10f; // fraction of dt to be adjusted
float time_adjust;
if (m_adjust_time_by >= 0) // make it run faster
{
time_adjust = dt * FRACTION;
if (time_adjust > m_adjust_time_by) time_adjust = m_adjust_time_by;
if (m_adjust_time_by > 0)
Log::verbose("info", "At %f %f adjusting time by %f dt %f to dt %f for %f",
World::getWorld()->getTime(), StkTime::getRealTime(),
time_adjust, dt, dt - time_adjust, m_adjust_time_by);
}
else // m_adjust_time negative, i.e. will go slower
{
time_adjust = -dt * FRACTION;
if (time_adjust < m_adjust_time_by) time_adjust = m_adjust_time_by;
Log::verbose("info", "At %f %f adjusting time by %f dt %f to dt %f for %f",
World::getWorld()->getTime(), StkTime::getRealTime(),
time_adjust, dt, dt - time_adjust, m_adjust_time_by);
}
m_adjust_time_by -= time_adjust;
dt -= time_adjust;
return dt;
} // adjustDT
//-----------------------------------------------------------------------------
/** Called on the client when it receives a notification from the server that
* all clients (and server) are ready to start the race. The server will
@ -460,7 +498,8 @@ void WorldStatus::pause(Phase phase)
m_phase = phase;
IrrlichtDevice *device = irr_driver->getDevice();
if (!device->getTimer()->isStopped())
if (!device->getTimer()->isStopped() &&
!NetworkConfig::get()->isNetworking())
device->getTimer()->stop();
} // pause
@ -475,6 +514,7 @@ void WorldStatus::unpause()
m_previous_phase = UNDEFINED_PHASE;
IrrlichtDevice *device = irr_driver->getDevice();
if (device->getTimer()->isStopped())
if (device->getTimer()->isStopped() &&
!NetworkConfig::get()->isNetworking())
device->getTimer()->start();
} // unpause

View File

@ -103,6 +103,14 @@ private:
/** The third sound to be played in ready, set, go. */
SFXBase *m_start_sound;
/** In networked game the world clock might be adjusted (without the
* player noticing), e.g. if a client causes rewinds in the server,
* that client needs to speed up to be further ahead of the server
* and so reduce the number of rollbacks. This is the amount of time
* by which the client's clock needs to be adjusted (positive or
* negative). */
float m_adjust_time_by;
/** The clock mode: normal counting forwards, or countdown */
ClockType m_clock_mode;
protected:
@ -126,13 +134,14 @@ private:
float m_count_up_timer;
bool m_engines_started;
void startEngines();
/** In networked game a client must wait for the server to start 'ready
* set go' to make sure all client are actually ready to start the game.
* A server on the other hand will run behind all clients, so it will
* wait for all clients to indicate that they have started the race. */
* set go' to make sure all client are actually ready to start the game.
* A server on the other hand will run behind all clients, so it will
* wait for all clients to indicate that they have started the race. */
bool m_server_is_ready;
void startEngines();
public:
WorldStatus();
virtual ~WorldStatus();
@ -146,6 +155,7 @@ public:
virtual void enterRaceOverState();
virtual void terminateRace();
void setTime(const float time);
float adjustDT(float dt);
// ------------------------------------------------------------------------
// Note: GO_PHASE is both: start phase and race phase
@ -196,7 +206,10 @@ public:
float getTimeSinceStart() const { return m_count_up_timer; }
// ------------------------------------------------------------------------
void setReadyToRace() { m_server_is_ready = true; }
// ------------------------------------------------------------------------
/** Sets a time by which the clock should be adjusted. Used by networking
* if too many rewinds are detected. */
void setAdjustTime(float t) { m_adjust_time_by = t; }
}; // WorldStatus

View File

@ -45,17 +45,26 @@ Synchronised<FILE*>Network::m_log_file = NULL;
* \param channel_limit : The maximum number of channels per peer.
* \param max_incoming_bandwidth : The maximum incoming bandwidth.
* \param max_outgoing_bandwidth : The maximum outgoing bandwidth.
* \param change_port_if_bound : Use another port if the prefered port is
* already bound to a socket.
*/
Network::Network(int peer_count, int channel_limit,
uint32_t max_incoming_bandwidth,
uint32_t max_outgoing_bandwidth,
ENetAddress* address)
ENetAddress* address, bool change_port_if_bound)
{
m_host = enet_host_create(address, peer_count, channel_limit, 0, 0);
if (!m_host)
if (m_host)
return;
if (change_port_if_bound)
{
Log::fatal("Network", "An error occurred while trying to create an "
"ENet client host.");
Log::warn("Network", "%d port is in used, use another port",
address->port);
ENetAddress new_addr;
new_addr.host = address->host;
// Any port
new_addr.port = 0;
m_host = enet_host_create(&new_addr, peer_count, channel_limit, 0, 0);
}
} // Network
@ -215,4 +224,4 @@ void Network::closeLog()
m_log_file.getData() = NULL;
m_log_file.unlock();
}
} // closeLog
} // closeLog

View File

@ -54,7 +54,8 @@ public:
Network(int peer_count, int channel_limit,
uint32_t max_incoming_bandwidth,
uint32_t max_outgoing_bandwidth,
ENetAddress* address);
ENetAddress* address,
bool change_port_if_bound = false);
virtual ~Network();
static void openLog();

View File

@ -17,6 +17,7 @@
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "network/network_config.hpp"
#include "online/xml_request.hpp"
NetworkConfig *NetworkConfig::m_network_config = NULL;
bool NetworkConfig::m_disable_lan = false;
@ -36,31 +37,21 @@ bool NetworkConfig::m_disable_lan = false;
NetworkConfig::NetworkConfig()
{
m_network_type = NETWORK_NONE;
m_auto_connect = false;
m_is_server = false;
m_is_public_server = false;
m_max_players = 4;
m_is_registered = false;
m_direct_connect = false;
m_cur_user_id = 0;
m_cur_user_token = "";
m_server_name = "";
m_password = "";
m_server_discovery_port = 2757;
m_server_port = 2758;
m_client_port = 2759;
m_my_address.lock();
m_my_address.getData().clear();
m_my_address.unlock();
} // NetworkConfig
//-----------------------------------------------------------------------------
/** Stores the public address of this host.
*/
void NetworkConfig::setMyAddress(const TransportAddress& addr)
{
m_my_address.lock();
m_my_address.getData().copy(addr);
m_my_address.unlock();
} // setPublicAddress
// --------------------------------------------------------------------
// ----------------------------------------------------------------------------
/** Sets if this instance is a server or client. It also assigns the
* private port depending if this is a server or client.
*/
@ -68,3 +59,80 @@ void NetworkConfig::setIsServer(bool b)
{
m_is_server = b;
} // setIsServer
// ----------------------------------------------------------------------------
unsigned NetworkConfig::getServerGameMode(RaceManager::MinorRaceModeType minor,
RaceManager::MajorRaceModeType major)
{
if (major == RaceManager::MAJOR_MODE_GRAND_PRIX)
{
if (minor == RaceManager::MINOR_MODE_NORMAL_RACE)
return 0;
else if (minor == RaceManager::MINOR_MODE_TIME_TRIAL)
return 1;
else if (minor == RaceManager::MINOR_MODE_FOLLOW_LEADER)
return 2;
}
else
{
if (minor == RaceManager::MINOR_MODE_NORMAL_RACE)
return 3;
else if (minor == RaceManager::MINOR_MODE_TIME_TRIAL)
return 4;
else if (minor == RaceManager::MINOR_MODE_FOLLOW_LEADER)
return 5;
else if (minor == RaceManager::MINOR_MODE_3_STRIKES)
return 6;
else if (minor == RaceManager::MINOR_MODE_SOCCER)
return 7;
}
return 0;
} // getServerGameMode
// ----------------------------------------------------------------------------
std::pair<RaceManager::MinorRaceModeType, RaceManager::MajorRaceModeType>
NetworkConfig::getLocalGameMode(unsigned id)
{
switch(id)
{
case 0:
return { RaceManager::MINOR_MODE_NORMAL_RACE,
RaceManager::MAJOR_MODE_GRAND_PRIX };
case 1:
return { RaceManager::MINOR_MODE_TIME_TRIAL,
RaceManager::MAJOR_MODE_GRAND_PRIX };
case 2:
return { RaceManager::MINOR_MODE_FOLLOW_LEADER,
RaceManager::MAJOR_MODE_GRAND_PRIX };
case 3:
return { RaceManager::MINOR_MODE_NORMAL_RACE,
RaceManager::MAJOR_MODE_SINGLE };
case 4:
return { RaceManager::MINOR_MODE_TIME_TRIAL,
RaceManager::MAJOR_MODE_SINGLE };
case 5:
return { RaceManager::MINOR_MODE_FOLLOW_LEADER,
RaceManager::MAJOR_MODE_SINGLE };
case 6:
return { RaceManager::MINOR_MODE_3_STRIKES,
RaceManager::MAJOR_MODE_SINGLE };
case 7:
return { RaceManager::MINOR_MODE_SOCCER,
RaceManager::MAJOR_MODE_SINGLE };
default:
break;
}
return { RaceManager::MINOR_MODE_NORMAL_RACE,
RaceManager::MAJOR_MODE_SINGLE };
} // getLocalGameMode
// ----------------------------------------------------------------------------
void NetworkConfig::setUserDetails(Online::XMLRequest* r,
const std::string& name)
{
assert(!m_cur_user_token.empty());
r->setApiURL(Online::API::SERVER_PATH, name);
r->addParameter("userid", m_cur_user_id);
r->addParameter("token", m_cur_user_token);
} // setUserDetails

View File

@ -23,10 +23,15 @@
#define HEADER_NETWORK_CONFIG
#include "network/transport_address.hpp"
#include "utils/synchronised.hpp"
#include "race/race_manager.hpp"
#include "irrString.h"
namespace Online
{
class XMLRequest;
}
class NetworkConfig
{
private:
@ -43,7 +48,9 @@ private:
/** If set it allows clients to connect directly to this server without
* using the stk server in between. It requires obviously that this
* server is accessible (through the firewall) from the outside. */
* server is accessible (through the firewall) from the outside,
* then you can use the \ref m_server_discovery_port and ip address for
* direct connection. */
bool m_is_public_server;
/** True if this host is a server, false otherwise. */
@ -52,12 +59,8 @@ private:
/** The password for a server (or to authenticate to a server). */
std::string m_password;
/** This is either this computer's public IP address, or the LAN
* address in case of a LAN game. With lock since it can
* be updated from a separate thread. */
Synchronised<TransportAddress> m_my_address;
/** The port number to which the server listens to detect LAN requests. */
/** The port number to which the server listens to detect direct socket
* requests. */
uint16_t m_server_discovery_port;
/** The port on which the server listens for connection requests from LAN. */
@ -69,13 +72,24 @@ private:
/** Maximum number of players on the server. */
int m_max_players;
/** If this is a server, it indicates if this server is registered
* with the stk server. */
bool m_is_registered;
/** True if STK was started with connect-now argument, so it use direct
* request-connection without using the addon server. */
bool m_direct_connect;
/** True if a client should connect to the first server it finds and
* immediately start a race. */
bool m_auto_connect;
/** If this is a server, the server name. */
irr::core::stringw m_server_name;
/** Used by wan server. */
uint32_t m_cur_user_id;
std::string m_cur_user_token;
/** Used by client server to determine if the child server is created. */
std::string m_server_id_file;
NetworkConfig();
public:
@ -99,7 +113,6 @@ public:
} // destroy
// ------------------------------------------------------------------------
void setMyAddress(const TransportAddress& addr);
void setIsServer(bool b);
// ------------------------------------------------------------------------
/** Sets the port for server discovery. */
@ -156,57 +169,61 @@ public:
// ------------------------------------------------------------------------
/** Sets the maximum number of players for this server. */
void setMaxPlayers(int n) { m_max_players = n; }
// --------------------------------------------------------------------
// ------------------------------------------------------------------------
/** Returns the maximum number of players for this server. */
int getMaxPlayers() const { return m_max_players; }
// --------------------------------------------------------------------
// ------------------------------------------------------------------------
/** Returns if this instance is a server. */
bool isServer() const { return m_is_server; }
// --------------------------------------------------------------------
// ------------------------------------------------------------------------
/** Returns if this instance is a client. */
bool isClient() const { return !m_is_server; }
// --------------------------------------------------------------------
// ------------------------------------------------------------------------
/** Sets the name of this server. */
void setServerName(const irr::core::stringw &name)
{
m_server_name = name;
} // setServerName
// --------------------------------------------------------------------
// ------------------------------------------------------------------------
/** Returns the server name. */
const irr::core::stringw& getServerName() const
{
assert(isServer());
return m_server_name;
} // getServerName
// --------------------------------------------------------------------
/** Sets if this server is registered with the stk server. */
void setRegistered(bool registered)
{
assert(isServer());
m_is_registered = registered;
} // setRegistered
// --------------------------------------------------------------------
/** Returns if this server is registered with the stk server. */
bool isRegistered() const
{
assert(isServer());
return m_is_registered;
} // isRegistered
// --------------------------------------------------------------------
/** Returns the IP address of this host. We need to return a copy
* to make sure the address is thread safe (otherwise it could happen
* that e.g. data is taken when the IP address was written, but not
return a;
* yet the port). */
const TransportAddress getMyAddress() const
{
TransportAddress a;
m_my_address.lock();
a.copy(m_my_address.getData());
m_my_address.unlock();
return a;
} // getMyAddress
// ------------------------------------------------------------------------
/** Sets if a client should immediately connect to the first server. */
void setAutoConnect(bool b) { m_auto_connect = b; }
// ------------------------------------------------------------------------
/** Returns if an immediate connection to the first server was
* requested. */
bool isAutoConnect() const { return m_auto_connect; }
// ------------------------------------------------------------------------
/** Returns the game mode id for server database. */
unsigned getServerGameMode(RaceManager::MinorRaceModeType mode,
RaceManager::MajorRaceModeType);
// ------------------------------------------------------------------------
/** Returns the minor and majar game mode from server database id. */
std::pair<RaceManager::MinorRaceModeType, RaceManager::MajorRaceModeType>
getLocalGameMode(unsigned);
// ------------------------------------------------------------------------
void setDirectConnect(bool val) { m_direct_connect = val; }
// ------------------------------------------------------------------------
bool isDirectConnect() const { return m_direct_connect; }
// ------------------------------------------------------------------------
void setCurrentUserId(uint32_t id) { m_cur_user_id = id ; }
// ------------------------------------------------------------------------
void setCurrentUserToken(const std::string& t) { m_cur_user_token = t; }
// ------------------------------------------------------------------------
uint32_t getCurrentUserId() const { return m_cur_user_id; }
// ------------------------------------------------------------------------
const std::string& getCurrentUserToken() const { return m_cur_user_token; }
// ------------------------------------------------------------------------
void setUserDetails(Online::XMLRequest* r, const std::string& name);
// ------------------------------------------------------------------------
void setServerIdFile(const std::string& id) { m_server_id_file = id; }
// ------------------------------------------------------------------------
const std::string& getServerIdFile() const { return m_server_id_file; }
}; // class NetworkConfig

View File

@ -16,87 +16,64 @@
// 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/network_console.hpp"
#include "main_loop.hpp"
#include "network/network_config.hpp"
#include "network/network_player_profile.hpp"
#include "network/stk_host.hpp"
#include "network/protocols/client_lobby.hpp"
#include "network/protocols/server_lobby.hpp"
#include "network/protocols/stop_server.hpp"
#include "network/stk_peer.hpp"
#include "utils/log.hpp"
#include "utils/time.hpp"
#include "utils/vs.hpp"
#include "main_loop.hpp"
#include <iostream>
NetworkConsole::NetworkConsole()
namespace NetworkConsole
{
m_localhost = NULL;
m_thread_keyboard = NULL;
}
// ----------------------------------------------------------------------------
void kickAllPlayers(STKHost* host)
{
const std::vector<STKPeer*> &peers = host->getPeers();
for (unsigned int i = 0; i < peers.size(); i++)
{
peers[i]->disconnect();
}
} // kickAllPlayers
// ----------------------------------------------------------------------------
NetworkConsole::~NetworkConsole()
{
#ifndef ANDROID
if (m_thread_keyboard)
pthread_cancel(*m_thread_keyboard);//, SIGKILL);
#endif
}
// ----------------------------------------------------------------------------
void NetworkConsole::run()
{
// listen keyboard console input
m_thread_keyboard = new pthread_t;
pthread_create(m_thread_keyboard, NULL, mainLoop, this);
Log::info("NetworkConsole", "Ready.");
} // run
// ----------------------------------------------------------------------------
void* NetworkConsole::mainLoop(void* data)
void mainLoop(STKHost* host)
{
VS::setThreadName("NetworkConsole");
NetworkConsole *me = static_cast<NetworkConsole*>(data);
std::string str = "";
bool stop = false;
while (!stop)
while (!host->requestedShutdown())
{
getline(std::cin, str);
if (str == "quit")
{
stop = true;
host->requestShutdown();
}
else if (str == "kickall" && NetworkConfig::get()->isServer())
{
me->kickAllPlayers();
kickAllPlayers(host);
}
else if (str == "start" && NetworkConfig::get()->isServer())
{
ServerLobby* protocol =
dynamic_cast<ServerLobby*>(LobbyProtocol::get());
protocol->signalRaceStartToClients();
auto sl = LobbyProtocol::get<ServerLobby>();
sl->signalRaceStartToClients();
}
else if (str == "selection" && NetworkConfig::get()->isServer())
{
ServerLobby* protocol =
dynamic_cast<ServerLobby*>(LobbyProtocol::get());
protocol->startSelection();
auto sl = LobbyProtocol::get<ServerLobby>();
sl->startSelection();
}
else if (str == "select" && NetworkConfig::get()->isClient())
{
std::string str2;
getline(std::cin, str2);
ServerLobby* protocol =
dynamic_cast<ServerLobby*>(LobbyProtocol::get());
ClientLobby* clrp = dynamic_cast<ClientLobby*>(protocol);
auto clrp = LobbyProtocol::get<ClientLobby>();
std::vector<NetworkPlayerProfile*> players =
STKHost::get()->getMyPlayerProfiles();
host->getMyPlayerProfiles();
// For now send a vote for each local player
for(unsigned int i=0; i<players.size(); i++)
{
@ -109,11 +86,9 @@ void* NetworkConsole::mainLoop(void* data)
std::cout << "Vote for ? (track/laps/reversed/major/minor/race#) :";
std::string str2;
getline(std::cin, str2);
LobbyProtocol* protocol = LobbyProtocol::get();
ClientLobby* clrp =
dynamic_cast<ClientLobby*>(protocol);
auto clrp = LobbyProtocol::get<ClientLobby>();
std::vector<NetworkPlayerProfile*> players =
STKHost::get()->getMyPlayerProfiles();
host->getMyPlayerProfiles();
if (str2 == "track")
{
std::cin >> str2;
@ -163,26 +138,7 @@ void* NetworkConsole::mainLoop(void* data)
Log::info("Console", "Unknown command '%s'.", str.c_str());
}
} // while !stop
Protocol *p = new StopServer();
while(p->getState() != PROTOCOL_STATE_TERMINATED)
{
StkTime::sleep(1);
}
delete p;
main_loop->abort();
return NULL;
} // mainLoop
// ----------------------------------------------------------------------------
void NetworkConsole::kickAllPlayers()
{
const std::vector<STKPeer*> &peers = STKHost::get()->getPeers();
for (unsigned int i = 0; i < peers.size(); i++)
{
peers[i]->disconnect();
}
} // kickAllPlayers
}

View File

@ -19,38 +19,11 @@
#ifndef HEADER_NETWORK_CONSOLE_HPP
#define HEADER_NETWORK_CONSOLE_HPP
#include "utils/types.hpp"
#include "pthread.h"
class NetworkString;
class STKHost;
class NetworkConsole
namespace NetworkConsole
{
protected:
STKHost *m_localhost;
pthread_t* m_thread_keyboard;
uint8_t m_max_players;
static void* mainLoop(void* data);
public:
NetworkConsole();
virtual ~NetworkConsole();
virtual void run();
void kickAllPlayers();
// ------------------------------------------------------------------------
void setMaxPlayers(uint8_t count) { m_max_players = count; }
// ------------------------------------------------------------------------
uint8_t getMaxPlayers() { return m_max_players; }
// ------------------------------------------------------------------------
virtual bool isServer() { return true; }
void mainLoop(STKHost* host);
}; // class NetworkConsole
#endif // SERVER_CONSOLE_HPP

View File

@ -154,6 +154,22 @@ public:
/** Returns a byte pointer to the content of the network string. */
const char* getData() const { return (char*)(m_buffer.data()); };
// ------------------------------------------------------------------------
/** Returns a byte pointer to the unread remaining content of the network
* string. */
char* getCurrentData()
{
return (char*)(m_buffer.data()+m_current_offset);
} // getCurrentData
// ------------------------------------------------------------------------
/** Returns a byte pointer to the unread remaining content of the network
* string. */
const char* getCurrentData() const
{
return (char*)(m_buffer.data()+m_current_offset);
} // getCurrentData
// ------------------------------------------------------------------------
/** Returns the remaining length of the network string. */
unsigned int size() const { return (int)m_buffer.size()-m_current_offset; }
@ -164,7 +180,7 @@ public:
{
m_current_offset += n;
assert(m_current_offset >=0 &&
m_current_offset < (int)m_buffer.size());
m_current_offset <= (int)m_buffer.size());
} // skip
// ------------------------------------------------------------------------
/** Returns the send size, which is the full length of the buffer. A
@ -233,7 +249,7 @@ public:
{
return addFloat(f);
} // add
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
/** Adds the xyz components of a Vec3 to the string. */
BareNetworkString& add(const Vec3 &xyz)
{
@ -353,6 +369,13 @@ public:
m_current_offset = 5; // ignore type and token
} // NetworkString
// ------------------------------------------------------------------------
/** Empties the string, but does not reset the pre-allocated size. */
void clear()
{
m_buffer.erase(m_buffer.begin() + 5, m_buffer.end());
m_current_offset = 5;
} // clear
// ------------------------------------------------------------------------
/** Returns the protocol type of this message. */
ProtocolType getProtocolType() const

View File

@ -37,7 +37,6 @@ Protocol::Protocol(ProtocolType type, CallbackObject* callback_object)
m_callback_object = callback_object;
m_type = type;
m_state = PROTOCOL_STATE_INITIALISING;
m_id = 0;
m_handle_connections = false;
m_handle_disconnections = false;
} // Protocol
@ -80,7 +79,8 @@ bool Protocol::checkDataSize(Event* event, unsigned int minimum_size)
*/
void Protocol::requestStart()
{
ProtocolManager::getInstance()->requestStart(this);
if (auto pm = ProtocolManager::lock())
pm->requestStart(shared_from_this());
} // requestStart
// ----------------------------------------------------------------------------
@ -88,7 +88,8 @@ void Protocol::requestStart()
*/
void Protocol::requestPause()
{
ProtocolManager::getInstance()->requestPause(this);
if (auto pm = ProtocolManager::lock())
pm->requestPause(shared_from_this());
} // requestPause
// ----------------------------------------------------------------------------
@ -96,7 +97,8 @@ void Protocol::requestPause()
*/
void Protocol::requestUnpause()
{
ProtocolManager::getInstance()->requestUnpause(this);
if (auto pm = ProtocolManager::lock())
pm->requestUnpause(shared_from_this());
} // requestUnpause
// ----------------------------------------------------------------------------
@ -104,23 +106,10 @@ void Protocol::requestUnpause()
*/
void Protocol::requestTerminate()
{
ProtocolManager::getInstance()->requestTerminate(this);
if (auto pm = ProtocolManager::lock())
pm->requestTerminate(shared_from_this());
} // requestTerminate
// ----------------------------------------------------------------------------
/** Finds a protocol with the given type and requests it to be terminated.
* If no such protocol exist, log an error message.
* \param type The protocol type to delete.
*/
void Protocol::findAndTerminateProtocol(ProtocolType type)
{
Protocol* protocol = ProtocolManager::getInstance()->getProtocol(type);
if (protocol)
protocol->requestTerminate();
else
Log::error("Protocol", "No protocol %d registered.", type);
} // findAndTerminateProtocol
// ----------------------------------------------------------------------------
/** Sends a message to all peers, inserting the peer's token into the message.
* The message is composed of a 1-byte message (usually the message type)

View File

@ -23,10 +23,10 @@
#ifndef PROTOCOL_HPP
#define PROTOCOL_HPP
#include "utils/leak_check.hpp"
#include "utils/no_copy.hpp"
#include "utils/types.hpp"
#include <memory>
#include <stddef.h>
class Event;
@ -45,12 +45,13 @@ enum ProtocolType
PROTOCOL_CONNECTION = 0x01, //!< Protocol that deals with client-server connection.
PROTOCOL_LOBBY_ROOM = 0x02, //!< Protocol that is used during the lobby room phase.
PROTOCOL_START_GAME = 0x03, //!< Protocol used when starting the game.
PROTOCOL_SYNCHRONIZATION = 0x04, //!<Protocol used to synchronize clocks.
PROTOCOL_SYNCHRONIZATION = 0x04, //!< Protocol used to determine latency
PROTOCOL_KART_UPDATE = 0x05, //!< Protocol to update karts position, rotation etc...
PROTOCOL_GAME_EVENTS = 0x06, //!< Protocol to communicate the game events.
PROTOCOL_CONTROLLER_EVENTS = 0x07, //!< Protocol to transfer controller modifications
PROTOCOL_SILENT = 0x08, //!< Used for protocols that do not subscribe to any network event.
PROTOCOL_MAX , //!< Maximum number of different protocol types
PROTOCOL_SYNCHRONOUS = 0x80, //!< Flag, indicates synchronous delivery
PROTOCOL_SILENT = 0xff //!< Used for protocols that do not subscribe to any network event.
}; // ProtocolType
// ----------------------------------------------------------------------------
@ -89,9 +90,9 @@ public:
* to make any network job.
* \ingroup network
*/
class Protocol : public NoCopy
class Protocol : public std::enable_shared_from_this<Protocol>,
public NoCopy
{
LEAK_CHECK()
protected:
/** The type of the protocol. */
ProtocolType m_type;
@ -102,9 +103,6 @@ protected:
/** The state this protocol is in (e.g. running, paused, ...). */
ProtocolState m_state;
/** The unique id of the protocol. */
uint32_t m_id;
/** True if this protocol should receive connection events. */
bool m_handle_connections;
@ -138,7 +136,6 @@ public:
void requestPause();
void requestUnpause();
void requestTerminate();
void findAndTerminateProtocol(ProtocolType type);
// ------------------------------------------------------------------------
/** \brief Called when the protocol is paused (by an other entity or by
@ -163,12 +160,6 @@ public:
/** Sets the current protocol state. */
void setState(ProtocolState s) { m_state = s; }
// ------------------------------------------------------------------------
/** Returns the unique protocol ID. */
uint32_t getId() const { return m_id; }
// ------------------------------------------------------------------------
/** Sets the unique protocol id. */
void setId(uint32_t id) { m_id = id; }
// ------------------------------------------------------------------------
/** \brief Notify a protocol matching the Event type of that event.
* \param event : Pointer to the event.
* \return True if the event has been treated, false otherwise. */

View File

@ -23,76 +23,93 @@
#include "network/stk_host.hpp"
#include "network/stk_peer.hpp"
#include "utils/log.hpp"
#include "utils/profiler.hpp"
#include "utils/time.hpp"
#include "utils/vs.hpp"
#include <assert.h>
#include <cstdlib>
#include <errno.h>
#include <functional>
#include <typeinfo>
ProtocolManager::ProtocolManager()
// ============================================================================
std::weak_ptr<ProtocolManager> ProtocolManager::m_protocol_manager;
// ============================================================================
std::shared_ptr<ProtocolManager> ProtocolManager::createInstance()
{
pthread_mutex_init(&m_asynchronous_protocols_mutex, NULL);
m_exit.setAtomic(false);
m_next_protocol_id.setAtomic(0);
m_asynchronous_update_thread = (pthread_t*)(malloc(sizeof(pthread_t)));
pthread_create(m_asynchronous_update_thread, NULL,
ProtocolManager::mainLoop, this);
} // ProtocolManager
if (!emptyInstance())
{
Log::fatal("ProtocolManager",
"Create only 1 instance of ProtocolManager!");
return NULL;
}
auto pm = std::make_shared<ProtocolManager>();
pm->m_asynchronous_update_thread = std::thread([pm]()
{
VS::setThreadName("ProtocolManager");
while(!pm->m_exit.load())
{
pm->asynchronousUpdate();
PROFILER_PUSH_CPU_MARKER("sleep", 0, 255, 255);
StkTime::sleep(2);
PROFILER_POP_CPU_MARKER();
}
});
m_protocol_manager = pm;
return pm;
} // createInstance
// ----------------------------------------------------------------------------
void* ProtocolManager::mainLoop(void* data)
ProtocolManager::ProtocolManager()
{
VS::setThreadName("ProtocolManager");
ProtocolManager* manager = static_cast<ProtocolManager*>(data);
while(manager && !manager->m_exit.getAtomic())
{
manager->asynchronousUpdate();
StkTime::sleep(2);
}
return NULL;
} // protocolManagerAsynchronousUpdate
m_exit.store(false);
m_all_protocols.resize(PROTOCOL_MAX);
} // ProtocolManager
// ----------------------------------------------------------------------------
ProtocolManager::~ProtocolManager()
{
// Now only this main thread is active, no more need for locks
for (unsigned int i = 0; i < m_all_protocols.size(); i++)
{
m_all_protocols[i].abort();
}
m_sync_events_to_process.lock();
for (EventList::iterator i =m_sync_events_to_process.getData().begin();
i!=m_sync_events_to_process.getData().end(); ++i)
delete *i;
m_sync_events_to_process.getData().clear();
m_sync_events_to_process.unlock();
m_async_events_to_process.lock();
for (EventList::iterator i = m_async_events_to_process.getData().begin();
i!= m_async_events_to_process.getData().end(); ++i)
delete *i;
m_async_events_to_process.getData().clear();
m_async_events_to_process.unlock();
m_requests.lock();
m_requests.getData().clear();
m_requests.unlock();
} // ~ProtocolManager
// ----------------------------------------------------------------------------
void ProtocolManager::OneProtocolType::abort()
{
m_protocols.getData().clear();
} // OneProtocolType::abort
// ----------------------------------------------------------------------------
/** \brief Stops the protocol manager.
*/
void ProtocolManager::abort()
{
m_exit.setAtomic(true);
pthread_mutex_lock(&m_asynchronous_protocols_mutex);
m_protocols.lock();
for (unsigned int i = 0; i < m_protocols.getData().size() ; i++)
delete m_protocols.getData()[i];
m_protocols.getData().clear();
m_protocols.unlock();
m_events_to_process.lock();
for (unsigned int i = 0; i < m_events_to_process.getData().size() ; i++)
delete m_events_to_process.getData()[i];
m_events_to_process.getData().clear();
m_events_to_process.unlock();
m_requests.lock();
m_requests.getData().clear();
m_requests.unlock();
pthread_mutex_unlock(&m_asynchronous_protocols_mutex);
pthread_mutex_destroy(&m_asynchronous_protocols_mutex);
pthread_join(*m_asynchronous_update_thread, NULL); // wait the thread to finish
m_exit.store(true);
// wait the thread to finish
m_asynchronous_update_thread.join();
} // abort
// ----------------------------------------------------------------------------
@ -102,31 +119,36 @@ void ProtocolManager::abort()
*/
void ProtocolManager::propagateEvent(Event* event)
{
m_events_to_process.lock();
m_events_to_process.getData().push_back(event);
m_events_to_process.unlock();
if (event->isSynchronous())
{
m_sync_events_to_process.lock();
m_sync_events_to_process.getData().push_back(event);
m_sync_events_to_process.unlock();
}
else
{
m_async_events_to_process.lock();
m_async_events_to_process.getData().push_back(event);
m_async_events_to_process.unlock();
}
return;
} // propagateEvent
// ----------------------------------------------------------------------------
/** \brief Asks the manager to start a protocol.
* This function will store the request, and process it at a time it is
* This function will store the request, and process it at a time when it is
* thread-safe.
* \param protocol : A pointer to the protocol to start
* \return The unique id of the protocol that is being started.
*/
uint32_t ProtocolManager::requestStart(Protocol* protocol)
void ProtocolManager::requestStart(std::shared_ptr<Protocol> protocol)
{
// assign a unique id to the protocol.
protocol->setId(getNextProtocolId());
// create the request
ProtocolRequest req(PROTOCOL_REQUEST_START, protocol);
// add it to the request stack
m_requests.lock();
m_requests.getData().push_back(req);
m_requests.unlock();
return req.getProtocol()->getId();
} // requestStart
// ----------------------------------------------------------------------------
@ -135,7 +157,7 @@ uint32_t ProtocolManager::requestStart(Protocol* protocol)
* thread-safe.
* \param protocol : A pointer to the protocol to pause
*/
void ProtocolManager::requestPause(Protocol* protocol)
void ProtocolManager::requestPause(std::shared_ptr<Protocol> protocol)
{
if (!protocol)
return;
@ -153,12 +175,12 @@ void ProtocolManager::requestPause(Protocol* protocol)
* thread-safe.
* \param protocol : A pointer to the protocol to unpause
*/
void ProtocolManager::requestUnpause(Protocol* protocol)
void ProtocolManager::requestUnpause(std::shared_ptr<Protocol> protocol)
{
if (!protocol)
return;
// create the request
ProtocolRequest req(PROTOCOL_REQUEST_UNPAUSE, protocol);;
ProtocolRequest req(PROTOCOL_REQUEST_UNPAUSE, protocol);
// add it to the request stack
m_requests.lock();
m_requests.getData().push_back(req);
@ -171,7 +193,7 @@ void ProtocolManager::requestUnpause(Protocol* protocol)
* thread-safe.
* \param protocol : A pointer to the protocol that is finished
*/
void ProtocolManager::requestTerminate(Protocol* protocol)
void ProtocolManager::requestTerminate(std::shared_ptr<Protocol> protocol)
{
if (!protocol)
return;
@ -193,25 +215,24 @@ void ProtocolManager::requestTerminate(Protocol* protocol)
} // requestTerminate
// ----------------------------------------------------------------------------
/** \brief Starts a protocol.
* Add the protocol info to the m_protocols vector.
* \param protocol : ProtocolInfo to start.
*/
void ProtocolManager::startProtocol(Protocol *protocol)
void ProtocolManager::startProtocol(std::shared_ptr<Protocol> protocol)
{
// add the protocol to the protocol vector so that it's updated
m_protocols.lock();
pthread_mutex_lock(&m_asynchronous_protocols_mutex);
Log::info("ProtocolManager",
"A %s protocol with id=%u has been started. There are %ld protocols running.",
typeid(*protocol).name(), protocol->getId(),
m_protocols.getData().size()+1);
m_protocols.getData().push_back(protocol);
// setup the protocol and notify it that it's started
assert(std::this_thread::get_id() == m_asynchronous_update_thread.get_id());
OneProtocolType &opt = m_all_protocols[protocol->getProtocolType()];
opt.lock();
opt.addProtocol(protocol);
protocol->setup();
protocol->setState(PROTOCOL_STATE_RUNNING);
m_protocols.unlock();
pthread_mutex_unlock(&m_asynchronous_protocols_mutex);
opt.unlock();
Log::info("ProtocolManager",
"A %s protocol has been started.", typeid(*protocol).name());
// setup the protocol and notify it that it's started
} // startProtocol
// ----------------------------------------------------------------------------
@ -219,11 +240,16 @@ void ProtocolManager::startProtocol(Protocol *protocol)
* Pauses a protocol and tells it that it's being paused.
* \param protocol : Protocol to pause.
*/
void ProtocolManager::pauseProtocol(Protocol *protocol)
void ProtocolManager::pauseProtocol(std::shared_ptr<Protocol> protocol)
{
assert(std::this_thread::get_id() == m_asynchronous_update_thread.get_id());
assert(protocol->getState() == PROTOCOL_STATE_RUNNING);
// We lock the protocol to avoid that paused() is called at the same
// time that the main thread delivers an event or calls update
m_all_protocols[protocol->getProtocolType()].lock();
protocol->setState(PROTOCOL_STATE_PAUSED);
protocol->paused();
m_all_protocols[protocol->getProtocolType()].unlock();
} // pauseProtocol
// ----------------------------------------------------------------------------
@ -231,86 +257,156 @@ void ProtocolManager::pauseProtocol(Protocol *protocol)
* Unpauses a protocol and notifies it.
* \param protocol : Protocol to unpause.
*/
void ProtocolManager::unpauseProtocol(Protocol *protocol)
void ProtocolManager::unpauseProtocol(std::shared_ptr<Protocol> protocol)
{
assert(std::this_thread::get_id() == m_asynchronous_update_thread.get_id());
assert(protocol->getState() == PROTOCOL_STATE_PAUSED);
// No lock necessary, since the protocol is paused, no other thread will
// be executing
protocol->setState(PROTOCOL_STATE_RUNNING);
protocol->unpaused();
} // unpauseProtocol
// ----------------------------------------------------------------------------
/** Removes a protocol from the list of protocols of a certain type.
* Note that the protocol is not deleted.
* \param p The protocol to be removed.
*/
void ProtocolManager::OneProtocolType::removeProtocol(std::shared_ptr<Protocol> p)
{
auto i = std::find(m_protocols.getData().begin(),
m_protocols.getData().end(), p);
if (i == m_protocols.getData().end())
{
Log::error("ProtocolManager",
"Trying to delete protocol '%s', which was not found",
typeid(*p).name());
}
else
{
m_protocols.getData().erase(i);
}
} // deleteProtocol
// ----------------------------------------------------------------------------
/** \brief Notes that a protocol is terminated.
* Remove a protocol from the protocols vector.
* \param protocol : Protocol concerned.
*/
void ProtocolManager::terminateProtocol(Protocol *protocol)
void ProtocolManager::terminateProtocol(std::shared_ptr<Protocol> protocol)
{
// Be sure that noone accesses the protocols vector while we erase a protocol
m_protocols.lock();
pthread_mutex_lock(&m_asynchronous_protocols_mutex);
int offset = 0;
std::string protocol_type = typeid(*protocol).name();
for (unsigned int i = 0; i < m_protocols.getData().size(); i++)
{
if (m_protocols.getData()[i-offset] == protocol)
{
protocol->setState(PROTOCOL_STATE_TERMINATED);
m_protocols.getData().erase(m_protocols.getData().begin()+(i-offset),
m_protocols.getData().begin()+(i-offset)+1);
offset++;
}
}
Log::info("ProtocolManager",
"A %s protocol has been terminated. There are %ld protocols running.",
protocol_type.c_str(), m_protocols.getData().size());
pthread_mutex_unlock(&m_asynchronous_protocols_mutex);
m_protocols.unlock();
assert(std::this_thread::get_id() == m_asynchronous_update_thread.get_id());
OneProtocolType &opt = m_all_protocols[protocol->getProtocolType()];
// Be sure that noone accesses the protocols vector
// while the protocol is being removed.
opt.lock();
opt.removeProtocol(protocol);
opt.unlock();
protocol->setState(PROTOCOL_STATE_TERMINATED);
protocol->terminated();
Log::info("ProtocolManager",
"A %s protocol has been terminated.", typeid(*protocol).name());
} // terminateProtocol
// ----------------------------------------------------------------------------
/** Sends the event to the corresponding protocol.
/** Requests to terminate all protocols of the given protocol type.
* This function must be called from the ProtocolManager thread in order
* to avoid a race condition (only the ProtocolManager thread can change the
* number of elements in that list).
*/
void ProtocolManager::OneProtocolType::requestTerminateAll()
{
for (unsigned int i = 0; i < m_protocols.getData().size(); i++)
{
m_protocols.getData()[i]->requestTerminate();
}
} // requestTerminateAll
// ----------------------------------------------------------------------------
/** Finds a protocol with the given type and requests it to be terminated.
* If no such protocol exist, log an error message.
* \param type The protocol type to delete.
*/
void ProtocolManager::findAndTerminate(ProtocolType type)
{
OneProtocolType &opt = m_all_protocols[type];
if (opt.isEmpty())
Log::error("ProtocolManager",
"findAndTerminate: No protocol %d registered.", type);
opt.requestTerminateAll();
} // findAndTerminate
// ----------------------------------------------------------------------------
/** Calls either notifyEvent(event) or notifyEventAsynchronous(evet) on all
* protocols. Note that no locking is done, it is the responsibility of the
* caller to avoid race conditions.
* \param event The event to deliver to the protocols.
*/
bool ProtocolManager::OneProtocolType::notifyEvent(Event *event)
{
if (m_protocols.getData().empty()) return false;
// Either all protocols of a certain type handle connects, or none.
// So we tet only one of them
if (event->getType() == EVENT_TYPE_CONNECTED &&
!m_protocols.getData()[0]->handleConnects()) return false;
if (event->getType() == EVENT_TYPE_DISCONNECTED &&
!m_protocols.getData()[0]->handleDisconnects()) return false;
bool can_be_deleted = false;
for (unsigned int i = 0; i < m_protocols.getData().size(); i++)
{
bool done = event->isSynchronous()
? m_protocols.getData()[i]->notifyEvent(event)
: m_protocols.getData()[i]->notifyEventAsynchronous(event);
can_be_deleted |= done;
}
return can_be_deleted;
} // notifyEvent
// ----------------------------------------------------------------------------
/** Sends the event to the corresponding protocol. Returns true if the event
* can be ignored, or false otherwise.
*/
bool ProtocolManager::sendEvent(Event* event)
{
m_protocols.lock();
int count=0;
for(unsigned int i=0; i<m_protocols.getData().size(); i++)
bool can_be_deleted = false;
if (event->getType() == EVENT_TYPE_MESSAGE)
{
Protocol *p = m_protocols.getData()[i];
bool is_right_protocol = false;
switch(event->getType())
{
case EVENT_TYPE_MESSAGE:
is_right_protocol = event->data().getProtocolType()==p->getProtocolType();
break;
case EVENT_TYPE_DISCONNECTED:
is_right_protocol = p->handleDisconnects();
break;
case EVENT_TYPE_CONNECTED:
is_right_protocol = p->handleConnects();
break;
} // switch event->getType()
if( is_right_protocol)
{
count ++;
event->isSynchronous() ? p->notifyEvent(event)
: p->notifyEventAsynchronous(event);
}
} // for i in protocols
m_protocols.unlock();
if (count>0 || StkTime::getTimeSinceEpoch()-event->getArrivalTime()
>= TIME_TO_KEEP_EVENTS )
{
delete event;
return true;
OneProtocolType &opt = m_all_protocols[event->data().getProtocolType()];
can_be_deleted = opt.notifyEvent(event);
}
return false;
else // connect or disconnect event --> test all protocols
{
for (unsigned int i = 0; i < m_all_protocols.size(); i++)
{
can_be_deleted |= m_all_protocols[i].notifyEvent(event);
}
}
return can_be_deleted || StkTime::getTimeSinceEpoch() - event->getArrivalTime()
>= TIME_TO_KEEP_EVENTS;
} // sendEvent
// ----------------------------------------------------------------------------
/** Calls either the synchronous update or asynchronous update function in all
* protocols of this type.
* \param dt Time step size.
* \param async True if asynchronousUpdate() should be called.
*/
void ProtocolManager::OneProtocolType::update(float dt, bool async)
{
for (unsigned int i = 0; i < m_protocols.getData().size(); i++)
{
if (m_protocols.getData()[i]->getState() == PROTOCOL_STATE_RUNNING)
{
async ? m_protocols.getData()[i]->asynchronousUpdate()
: m_protocols.getData()[i]->update(dt);
}
}
} // update
// ----------------------------------------------------------------------------
/** \brief Updates the manager.
*
@ -323,32 +419,39 @@ bool ProtocolManager::sendEvent(Event* event)
*/
void ProtocolManager::update(float dt)
{
// Update from main thread only:
assert(std::this_thread::get_id() != m_asynchronous_update_thread.get_id());
// before updating, notify protocols that they have received events
m_events_to_process.lock();
int size = (int)m_events_to_process.getData().size();
int offset = 0;
for (int i = 0; i < size; i++)
m_sync_events_to_process.lock();
EventList::iterator i = m_sync_events_to_process.getData().begin();
while (i != m_sync_events_to_process.getData().end())
{
// Don't handle asynchronous events here.
if(!m_events_to_process.getData()[i+offset]->isSynchronous()) continue;
bool result = sendEvent(m_events_to_process.getData()[i+offset]);
if (result)
m_sync_events_to_process.unlock();
bool can_be_deleted = sendEvent(*i);
m_sync_events_to_process.lock();
if (can_be_deleted)
{
m_events_to_process.getData()
.erase(m_events_to_process.getData().begin()+(i+offset),
m_events_to_process.getData().begin()+(i+offset+1));
offset --;
delete *i;
i = m_sync_events_to_process.getData().erase(i);
}
else
{
// This should only happen if the protocol has not been started
++i;
}
}
m_events_to_process.unlock();
// now update all protocols
m_protocols.lock();
for (unsigned int i = 0; i < m_protocols.getData().size(); i++)
m_sync_events_to_process.unlock();
// Now update all protocols.
for (unsigned int i = 0; i < m_all_protocols.size(); i++)
{
if (m_protocols.getData()[i]->getState() == PROTOCOL_STATE_RUNNING)
m_protocols.getData()[i]->update(dt);
OneProtocolType &opt = m_all_protocols[i];
opt.lock();
opt.update(dt, /*async*/false);
opt.unlock();
}
m_protocols.unlock();
} // update
// ----------------------------------------------------------------------------
@ -362,44 +465,66 @@ void ProtocolManager::update(float dt)
*/
void ProtocolManager::asynchronousUpdate()
{
// before updating, notice protocols that they have received information
m_events_to_process.lock();
int size = (int)m_events_to_process.getData().size();
int offset = 0;
for (int i = 0; i < size; i++)
PROFILER_PUSH_CPU_MARKER("Message delivery", 255, 0, 0);
// First deliver asynchronous messages for all protocols
// =====================================================
m_async_events_to_process.lock();
EventList::iterator i = m_async_events_to_process.getData().begin();
while (i != m_async_events_to_process.getData().end())
{
// Don't handle synchronous events here.
if(m_events_to_process.getData()[i+offset]->isSynchronous()) continue;
bool result = sendEvent(m_events_to_process.getData()[i+offset]);
m_async_events_to_process.unlock();
m_all_protocols[(*i)->getType()].lock();
bool result = sendEvent(*i);
m_all_protocols[(*i)->getType()].unlock();
m_async_events_to_process.lock();
if (result)
{
m_events_to_process.getData()
.erase(m_events_to_process.getData().begin()+(i+offset),
m_events_to_process.getData().begin()+(i+offset+1));
offset --;
delete *i;
i = m_async_events_to_process.getData().erase(i);
}
}
m_events_to_process.unlock();
else
{
// This should only happen if the protocol has not been started
// or already terminated (e.g. late ping answer)
++i;
}
} // while i != m_events_to_process.end()
m_async_events_to_process.unlock();
// now update all protocols that need to be updated in asynchronous mode
pthread_mutex_lock(&m_asynchronous_protocols_mutex);
// FIXME: does m_protocols need to be locked???
for (unsigned int i = 0; i < m_protocols.getData().size(); i++)
PROFILER_POP_CPU_MARKER();
PROFILER_PUSH_CPU_MARKER("Message delivery", 255, 0, 0);
// Second: update all running protocols
// ====================================
// Now update all protocols.
for (unsigned int i = 0; i < m_all_protocols.size(); i++)
{
if (m_protocols.getData()[i]->getState() == PROTOCOL_STATE_RUNNING)
m_protocols.getData()[i]->asynchronousUpdate();
OneProtocolType &opt = m_all_protocols[i];
// The lock is likely not necessary, since this function is only
// called from the ProtocolManager thread, and this thread is also
// the only one who changes the number of protocols.
// Edit: remove this lock can avoid hanging the GUI when connecting
// to or creating server, but you need to make sure async and non-async
// update in each protocol will have atomic or mutex write
//opt.lock();
opt.update(0, /*async*/true); // dt does not matter, so set it to 0
//opt.unlock();
}
pthread_mutex_unlock(&m_asynchronous_protocols_mutex);
// Process queued events for protocols
// these requests are asynchronous
PROFILER_POP_CPU_MARKER();
PROFILER_PUSH_CPU_MARKER("Process events", 0, 255, 0);
// Process queued events (start, pause, ...) for protocols asynchronously
// ======================================================================
m_requests.lock();
while(m_requests.getData().size()>0)
{
ProtocolRequest request = m_requests.getData()[0];
m_requests.getData().erase(m_requests.getData().begin());
m_requests.unlock();
// Make sure new requests can be queued up while handling requests.
m_requests.unlock();
// This is often used that terminating a protocol unpauses another,
// so the m_requests queue must not be locked while executing requests.
switch (request.getType())
@ -420,54 +545,18 @@ void ProtocolManager::asynchronousUpdate()
m_requests.lock();
} // while m_requests.size()>0
m_requests.unlock();
PROFILER_POP_CPU_MARKER();
} // asynchronousUpdate
// ----------------------------------------------------------------------------
/** \brief Get a protocol using its id.
* \param id : Unique ID of the seek protocol.
* \return The protocol that has the ID id.
*/
Protocol* ProtocolManager::getProtocol(uint32_t id)
{
// FIXME: does m_protocols need to be locked??
for (unsigned int i = 0; i < m_protocols.getData().size(); i++)
{
if (m_protocols.getData()[i]->getId() == id)
return m_protocols.getData()[i];
}
return NULL;
} // getProtocol
// ----------------------------------------------------------------------------
/** \brief Get a protocol using its type.
* \param type : The type of the protocol.
* \return The protocol that matches the given type.
*/
Protocol* ProtocolManager::getProtocol(ProtocolType type)
std::shared_ptr<Protocol> ProtocolManager::getProtocol(ProtocolType type)
{
// FIXME: Does m_protocols need to be locked?
for (unsigned int i = 0; i < m_protocols.getData().size(); i++)
{
if (m_protocols.getData()[i]->getProtocolType() == type)
return m_protocols.getData()[i];
}
return NULL;
OneProtocolType &opt = m_all_protocols[type];
if (opt.isEmpty()) return NULL;
return opt.getFirstProtocol();
} // getProtocol
// ----------------------------------------------------------------------------
/** \brief Assign an id to a protocol.
* This function will assign m_next_protocol_id as the protocol id.
* This id starts at 0 at the beginning and is increased by 1 each time
* a protocol starts.
* \param protocol_info : The protocol info that needs an id.
*/
uint32_t ProtocolManager::getNextProtocolId()
{
m_next_protocol_id.lock();
uint32_t id = m_next_protocol_id.getData();
m_next_protocol_id.getData()++;
m_next_protocol_id.unlock();
return id;
} // getNextProtocolId

View File

@ -30,7 +30,11 @@
#include "utils/synchronised.hpp"
#include "utils/types.hpp"
#include <atomic>
#include <list>
#include <memory>
#include <vector>
#include <thread>
class Event;
class STKPeer;
@ -53,7 +57,8 @@ enum ProtocolRequestType
// ----------------------------------------------------------------------------
/** \struct ProtocolRequest
* \brief Represents a request to do an action about a protocol.
* \brief Represents a request to do an action about a protocol, e.g. to
* start, pause, unpause or terminate a protocol.
*/
class ProtocolRequest
{
@ -62,10 +67,10 @@ public:
ProtocolRequestType m_type;
/** The concerned protocol information. */
Protocol *m_protocol;
std::shared_ptr<Protocol> m_protocol;
public:
ProtocolRequest(ProtocolRequestType type, Protocol *protocol)
ProtocolRequest(ProtocolRequestType type, std::shared_ptr<Protocol> protocol)
{
m_type = type;
m_protocol = protocol;
@ -75,7 +80,7 @@ public:
ProtocolRequestType getType() const { return m_type; }
// ------------------------------------------------------------------------
/** Returns the protocol for this request. */
Protocol *getProtocol() { return m_protocol; }
std::shared_ptr<Protocol> getProtocol() { return m_protocol; }
}; // class ProtocolRequest;
// ============================================================================
@ -85,68 +90,179 @@ public:
* This class is in charge of storing and managing protocols.
* It is a singleton as there can be only one protocol manager per game
* instance. Any game object that wants to start a protocol must create a
* protocol and give it to this singleton. The protocols are updated in a
* special thread, to ensure that they are processed independently from the
* frames per second. Then, the management of protocols is thread-safe: any
* object can start/pause/... protocols whithout problems.
* protocol and give it to this singleton. The protocols are updated in two
* different ways:
* 1) Asynchronous updates:
* A separate threads runs that delivers asynchronous events
* (i.e. messages), updates each protocol, and handles new requests
* (start/stop protocol etc). Protocols are updated using the
* Protocol::asynchronousUpdate() function.
* 2) Synchronous updates:
* This is called from the main game thread, and will deliver synchronous
* events (i.e. messages), and updates each protocol using
* Protocol::update().
*
* Since the STK main loop is not thread safe, any game changing events must
* (e.g. events that push a new screen, ...) be processed synchronoysly.
* On the other hand, asynchronous updates will be handled much more
* frequently, so synchronous updates should be avoided as much as possible.
* The sender selects if a message is synchronous or asynchronous. The
* network layer (separate thread) calls propagateEvent in the
* ProtocolManager, which will add the event to the synchronous or
* asynchornous queue.
* Protocol start/pause/... requests are also stored in a separate queue,
* which is thread-safe, and requests will be handled by the ProtocolManager
* thread, to ensure that they are processed independently from the
* frames per second.
*
* Events received by ENET are queried and then handled by STKHost::mainLoop.
* Besides messages these events also include connection and disconnection
* notifications. Protocols can decide to receives those notifications or
* not. The Enet events are converted into STK events, which store e.g. the
* sender as STKPeer info, and the message data is converted into a
* NetworkString. This STK event is then forwarded to the corresponding
* protocols.
*
* There are some protocols that can have more than one instance running at
* a time (e.g. on the server a connect to peer protocol). The Protocol
* Manager stores each protocol with the same protocol id in a OneProtocol
* structure (so in most cases this is just one protocol instance in one
* OneProtocol structure, but e.g. several connect_to_peer instances would
* be stored in one OneProtocoll instance. The OneProtocol instance is
* responsible to forward events to all protocols with the same id.
*
*/
class ProtocolManager : public AbstractSingleton<ProtocolManager>,
public NoCopy
class ProtocolManager : public NoCopy
{
friend class AbstractSingleton<ProtocolManager>;
private:
/** Contains the running protocols.
* This stores the protocols that are either running or paused, their
* state and their unique id. */
Synchronised<std::vector<Protocol*> >m_protocols;
/** A simple class that stores all protocols of a certain type. While
* many protocols have at most one instance running, some (e.g.
* GetPublicAddress, ConntectToPeer, ...) can have several instances
* active at the same time. */
class OneProtocolType
{
private:
Synchronised< std::vector<std::shared_ptr<Protocol> > > m_protocols;
public:
void removeProtocol(std::shared_ptr<Protocol> p);
void requestTerminateAll();
bool notifyEvent(Event *event);
void update(float dt, bool async);
void abort();
// --------------------------------------------------------------------
/** Returns the first protocol of a given type. It is assumed that
* there is a protocol of that type. */
std::shared_ptr<Protocol> getFirstProtocol()
{ return m_protocols.getData()[0]; }
// --------------------------------------------------------------------
/** Returns if this protocol class handles connect events. Protocols
* of the same class either all handle a connect event, or none, so
* only the first protocol is actually tested. */
bool handleConnects() const
{
return !m_protocols.getData().empty() &&
m_protocols.getData()[0]->handleConnects();
} // handleConnects
// --------------------------------------------------------------------
/** Returns if this protocol class handles disconnect events. Protocols
* of the same class either all handle a disconnect event, or none, so
* only the first protocol is actually tested. */
bool handleDisconnects() const
{
return !m_protocols.getData().empty() &&
m_protocols.getData()[0]->handleDisconnects();
} // handleDisconnects
// --------------------------------------------------------------------
/** Locks access to this list of all protocols of a certain type. */
void lock() { m_protocols.lock(); }
// --------------------------------------------------------------------
/** Locks access to this list of all protocols of a certain type. */
void unlock() { m_protocols.unlock(); }
// --------------------------------------------------------------------
void addProtocol(std::shared_ptr<Protocol> p)
{
m_protocols.getData().push_back(p);
} // addProtocol
// --------------------------------------------------------------------
/** Returns if there are no protocols of this type registered. */
bool isEmpty() const { return m_protocols.getData().empty(); }
// --------------------------------------------------------------------
}; // class OneProtocolType
// ------------------------------------------------------------------------
/** The list of all protocol types, each one containing a (potentially
* empty) list of protocols. */
std::vector<OneProtocolType> m_all_protocols;
/** A list of network events - messages, disconnect and disconnects. */
typedef std::list<Event*> EventList;
/** Contains the network events to pass synchronously to protocols
* (i.e. from the main thread). */
Synchronised<EventList> m_sync_events_to_process;
/** Contains the network events to pass asynchronously to protocols
* (i.e. from the separate ProtocolManager thread). */
Synchronised<std::vector<Event*> > m_events_to_process;
* (i.e. from the separate ProtocolManager thread). */
Synchronised<EventList> m_async_events_to_process;
/** Contains the requests to start/pause etc... protocols. */
Synchronised< std::vector<ProtocolRequest> > m_requests;
/*! \brief The next id to assign to a protocol.
* This value is incremented by 1 each time a protocol is started.
* If a protocol has an id lower than this value, it means that it has
* been formerly started.
*/
Synchronised<uint32_t> m_next_protocol_id;
/** When set to true, the main thread will exit. */
Synchronised<bool> m_exit;
// mutexes:
/*! Used to ensure that the protocol vector is used thread-safely. */
pthread_mutex_t m_asynchronous_protocols_mutex;
std::atomic_bool m_exit;
/*! Asynchronous update thread.*/
pthread_t* m_asynchronous_update_thread;
std::thread m_asynchronous_update_thread;
/*! Single instance of protocol manager.*/
static std::weak_ptr<ProtocolManager> m_protocol_manager;
ProtocolManager();
virtual ~ProtocolManager();
static void* mainLoop(void *data);
uint32_t getNextProtocolId();
bool sendEvent(Event* event);
virtual void startProtocol(Protocol *protocol);
virtual void terminateProtocol(Protocol *protocol);
virtual void startProtocol(std::shared_ptr<Protocol> protocol);
virtual void terminateProtocol(std::shared_ptr<Protocol> protocol);
virtual void asynchronousUpdate();
virtual void pauseProtocol(Protocol *protocol);
virtual void unpauseProtocol(Protocol *protocol);
virtual void pauseProtocol(std::shared_ptr<Protocol> protocol);
virtual void unpauseProtocol(std::shared_ptr<Protocol> protocol);
public:
virtual void abort();
virtual void propagateEvent(Event* event);
virtual uint32_t requestStart(Protocol* protocol);
virtual void requestPause(Protocol* protocol);
virtual void requestUnpause(Protocol* protocol);
virtual void requestTerminate(Protocol* protocol);
virtual void update(float dt);
virtual Protocol* getProtocol(uint32_t id);
virtual Protocol* getProtocol(ProtocolType type);
// ===========================================
// Public constructor is required for shared_ptr
ProtocolManager();
virtual ~ProtocolManager();
void abort();
void propagateEvent(Event* event);
std::shared_ptr<Protocol> getProtocol(ProtocolType type);
void requestStart(std::shared_ptr<Protocol> protocol);
void requestPause(std::shared_ptr<Protocol> protocol);
void requestUnpause(std::shared_ptr<Protocol> protocol);
void requestTerminate(std::shared_ptr<Protocol> protocol);
void findAndTerminate(ProtocolType type);
void update(float dt);
// ------------------------------------------------------------------------
bool isExiting() const { return m_exit.load(); }
// ------------------------------------------------------------------------
const std::thread& getThread() const
{
return m_asynchronous_update_thread;
} // getThreadID
// ------------------------------------------------------------------------
static std::shared_ptr<ProtocolManager> createInstance();
// ------------------------------------------------------------------------
static bool emptyInstance()
{
return m_protocol_manager.expired();
} // emptyInstance
// ------------------------------------------------------------------------
static std::shared_ptr<ProtocolManager> lock()
{
return m_protocol_manager.lock();
} // lock
}; // class ProtocolManager
#endif // PROTOCOL_MANAGER_HPP

View File

@ -19,6 +19,7 @@
#include "network/protocols/client_lobby.hpp"
#include "config/player_manager.hpp"
#include "karts/kart_properties_manager.hpp"
#include "modes/world_with_rank.hpp"
#include "network/event.hpp"
#include "network/network_config.hpp"
@ -33,6 +34,7 @@
#include "states_screens/network_kart_selection.hpp"
#include "states_screens/race_result_gui.hpp"
#include "states_screens/state_manager.hpp"
#include "tracks/track_manager.hpp"
#include "utils/log.hpp"
// ============================================================================
@ -237,6 +239,7 @@ bool ClientLobby::notifyEvent(Event* event)
message_type);
switch(message_type)
{
case LE_START_SELECTION: startSelection(event); break;
case LE_KART_SELECTION_UPDATE: kartSelectionUpdate(event); break;
case LE_LOAD_WORLD: loadWorld(); break;
case LE_RACE_FINISHED: raceFinished(event); break;
@ -266,7 +269,6 @@ bool ClientLobby::notifyEventAsynchronous(Event* event)
case LE_NEW_PLAYER_CONNECTED: newPlayer(event); break;
case LE_PLAYER_DISCONNECTED : disconnectedPlayer(event); break;
case LE_START_RACE: startGame(event); break;
case LE_START_SELECTION: startSelection(event); break;
case LE_CONNECTION_REFUSED: connectionRefused(event); break;
case LE_CONNECTION_ACCEPTED: connectionAccepted(event); break;
case LE_KART_SELECTION_REFUSED: kartSelectionRefused(event); break;
@ -321,6 +323,23 @@ void ClientLobby::update(float dt)
// 4 (size of id), global id
ns->addUInt8(LE_CONNECTION_REQUESTED).encodeString(name)
.encodeString(NetworkConfig::get()->getPassword());
auto all_k = kart_properties_manager->getAllAvailableKarts();
auto all_t = track_manager->getAllTrackIdentifiers();
if (all_k.size() >= 65536)
all_k.resize(65535);
if (all_t.size() >= 65536)
all_t.resize(65535);
ns->addUInt16((uint16_t)all_k.size()).addUInt16((uint16_t)all_t.size());
for (const std::string& kart : all_k)
{
ns->encodeString(kart);
}
for (const std::string& track : all_t)
{
ns->encodeString(track);
}
sendToServer(ns);
delete ns;
m_state = REQUESTING_CONNECTION;
@ -334,11 +353,11 @@ void ClientLobby::update(float dt)
{
NetworkKartSelectionScreen* screen =
NetworkKartSelectionScreen::getInstance();
screen->setAvailableKartsFromServer(m_available_karts);
screen->push();
m_state = SELECTING_KARTS;
Protocol *p = new LatencyProtocol();
p->requestStart();
std::make_shared<LatencyProtocol>()->requestStart();
Log::info("LobbyProtocol", "LatencyProtocol started.");
}
break;
@ -350,7 +369,7 @@ void ClientLobby::update(float dt)
break;
case DONE:
m_state = EXITING;
ProtocolManager::getInstance()->requestTerminate(this);
requestTerminate();
break;
case EXITING:
break;
@ -507,6 +526,15 @@ void ClientLobby::connectionAccepted(Event* event)
NetworkingLobby::getInstance()->addPlayer(profile);
m_server = event->getPeer();
m_state = CONNECTED;
if (NetworkConfig::get()->isAutoConnect())
{
// Send a message to the server to start
NetworkString start(PROTOCOL_LOBBY_ROOM);
start.setSynchronous(true);
start.addUInt8(LobbyProtocol::LE_REQUEST_BEGIN);
STKHost::get()->sendToServer(&start, true);
}
} // connectionAccepted
//-----------------------------------------------------------------------------
@ -538,6 +566,9 @@ void ClientLobby::connectionRefused(Event* event)
case 2:
Log::info("ClientLobby", "Client busy.");
break;
case 3:
Log::info("ClientLobby", "Having incompatible karts / tracks.");
break;
default:
Log::info("ClientLobby", "Connection refused.");
break;
@ -609,8 +640,7 @@ void ClientLobby::kartSelectionUpdate(Event* event)
//-----------------------------------------------------------------------------
/*! \brief Called when the server broadcasts to start the race.
race needs to be started.
/*! \brief Called when the server broadcasts to start the race to all clients.
* \param event : Event providing the information (no additional information
* in this case).
*/
@ -620,7 +650,8 @@ void ClientLobby::startGame(Event* event)
// Triggers the world finite state machine to go from WAIT_FOR_SERVER_PHASE
// to READY_PHASE.
World::getWorld()->setReadyToRace();
Log::info("ClientLobby", "Starting new game");
Log::info("ClientLobby", "Starting new game at %lf",
StkTime::getRealTime());
} // startGame
//-----------------------------------------------------------------------------
@ -636,6 +667,8 @@ void ClientLobby::startingRaceNow()
NetworkString *ns = getNetworkString(2);
ns->addUInt8(LE_STARTED_RACE);
sendToServer(ns, /*reliable*/true);
Log::verbose("ClientLobby", "StartingRaceNow at %lf",
StkTime::getRealTime());
terminateLatencyProtocol();
} // startingRaceNow
@ -647,6 +680,23 @@ void ClientLobby::startingRaceNow()
void ClientLobby::startSelection(Event* event)
{
m_state = KART_SELECTION;
const NetworkString& data = event->data();
const unsigned kart_num = data.getUInt16();
const unsigned track_num = data.getUInt16();
m_available_karts.clear();
m_available_tracks.clear();
for (unsigned i = 0; i < kart_num; i++)
{
std::string kart;
data.decodeString(&kart);
m_available_karts.insert(kart);
}
for (unsigned i = 0; i < track_num; i++)
{
std::string track;
data.decodeString(&track);
m_available_tracks.insert(track);
}
Log::info("ClientLobby", "Kart selection starts now");
} // startSelection
@ -671,29 +721,11 @@ void ClientLobby::raceFinished(Event* event)
"Server notified that the race is finished.");
// stop race protocols
Protocol* protocol = ProtocolManager::getInstance()
->getProtocol(PROTOCOL_CONTROLLER_EVENTS);
if (protocol)
ProtocolManager::getInstance()->requestTerminate(protocol);
else
Log::error("ClientLobby",
"No controller events protocol registered.");
protocol = ProtocolManager::getInstance()
->getProtocol(PROTOCOL_KART_UPDATE);
if (protocol)
ProtocolManager::getInstance()->requestTerminate(protocol);
else
Log::error("ClientLobby",
"No kart update protocol registered.");
protocol = ProtocolManager::getInstance()
->getProtocol(PROTOCOL_GAME_EVENTS);
if (protocol)
ProtocolManager::getInstance()->requestTerminate(protocol);
else
Log::error("ClientLobby",
"No game events protocol registered.");
auto pm = ProtocolManager::lock();
assert(pm);
pm->findAndTerminate(PROTOCOL_CONTROLLER_EVENTS);
pm->findAndTerminate(PROTOCOL_KART_UPDATE);
pm->findAndTerminate(PROTOCOL_GAME_EVENTS);
// finish the race
WorldWithRank* ranked_world = (WorldWithRank*)(World::getWorld());
@ -720,6 +752,16 @@ void ClientLobby::raceFinished(Event* event)
void ClientLobby::exitResultScreen(Event *event)
{
RaceResultGUI::getInstance()->backToLobby();
// Will be reset to linked if connected to server, see update(float dt)
m_game_setup = STKHost::get()->setupNewGame();
STKHost::get()->getServerPeerForClient()->unsetClientServerToken();
// stop race protocols
auto pm = ProtocolManager::lock();
assert(pm);
pm->findAndTerminate(PROTOCOL_CONTROLLER_EVENTS);
pm->findAndTerminate(PROTOCOL_KART_UPDATE);
pm->findAndTerminate(PROTOCOL_GAME_EVENTS);
m_state = NONE;
} // exitResultScreen
//-----------------------------------------------------------------------------

View File

@ -4,6 +4,7 @@
#include "network/protocols/lobby_protocol.hpp"
#include "network/transport_address.hpp"
#include "utils/cpp2011.hpp"
#include <set>
class STKPeer;
@ -49,6 +50,9 @@ private:
/** The state of the finite state machine. */
STATE m_state;
std::set<std::string> m_available_karts;
std::set<std::string> m_available_tracks;
public:
ClientLobby();
virtual ~ClientLobby();
@ -67,11 +71,18 @@ public:
void startingRaceNow();
void leave();
const std::set<std::string>& getAvailableKarts() const
{ return m_available_karts; }
const std::set<std::string>& getAvailableTracks() const
{ return m_available_tracks; }
virtual bool notifyEvent(Event* event) OVERRIDE;
virtual bool notifyEventAsynchronous(Event* event) OVERRIDE;
virtual void finishedLoadingWorld() OVERRIDE;
virtual void setup() OVERRIDE;
virtual void update(float dt) OVERRIDE;
virtual bool waitingForPlayers() const OVERRIDE
{ return m_state == LINKED; }
virtual void asynchronousUpdate() OVERRIDE {}
};

View File

@ -20,11 +20,8 @@
#include "network/event.hpp"
#include "network/network_config.hpp"
#include "network/protocols/get_public_address.hpp"
#include "network/protocols/get_peer_address.hpp"
#include "network/protocols/hide_public_address.hpp"
#include "network/protocols/request_connection.hpp"
#include "network/protocols/ping_protocol.hpp"
#include "network/protocol_manager.hpp"
#include "network/stk_host.hpp"
#include "utils/time.hpp"
@ -40,7 +37,6 @@ ConnectToPeer::ConnectToPeer(uint32_t peer_id) : Protocol(PROTOCOL_CONNECTION)
m_peer_address.clear();
m_peer_id = peer_id;
m_state = NONE;
m_current_protocol = NULL;
m_is_lan = false;
setHandleConnections(true);
} // ConnectToPeer(peer_id)
@ -55,8 +51,7 @@ ConnectToPeer::ConnectToPeer(const TransportAddress &address)
m_peer_address.copy(address);
// We don't need to find the peer address, so we can start
// with the state when we found the peer address.
m_state = RECEIVED_PEER_ADDRESS;
m_current_protocol = NULL;
m_state = WAIT_FOR_CONNECTION;
m_is_lan = true;
setHandleConnections(true);
} // ConnectToPeers(TransportAddress)
@ -68,14 +63,6 @@ ConnectToPeer::~ConnectToPeer()
} // ~ConnectToPeer
// ----------------------------------------------------------------------------
void ConnectToPeer::setup()
{
m_broadcast_count = 0;
m_time_last_broadcast = 0;
} // setup
// ----------------------------------------------------------------------------
bool ConnectToPeer::notifyEventAsynchronous(Event* event)
{
if (event->getType() == EVENT_TYPE_CONNECTED)
@ -99,94 +86,67 @@ void ConnectToPeer::asynchronousUpdate()
{
case NONE:
{
m_current_protocol = new GetPeerAddress(m_peer_id, this);
m_current_protocol = std::make_shared<GetPeerAddress>(m_peer_id);
m_current_protocol->requestStart();
// Pause this protocol till we receive an answer
// The GetPeerAddress protocol will change the state and
// unpause this protocol
requestPause();
m_state = RECEIVED_PEER_ADDRESS;
break;
}
case RECEIVED_PEER_ADDRESS:
{
if (m_peer_address.getIP() == 0 || m_peer_address.getPort() == 0)
// Wait until we have peer address
auto get_peer_address =
std::dynamic_pointer_cast<GetPeerAddress>(m_current_protocol);
assert(get_peer_address);
if (get_peer_address->getAddress().isUnset())
return;
m_peer_address.copy(get_peer_address->getAddress());
m_current_protocol = nullptr;
if (m_peer_address.isUnset())
{
Log::error("ConnectToPeer",
"The peer you want to connect to has hidden his address.");
m_state = DONE;
break;
}
delete m_current_protocol;
m_current_protocol = 0;
// Now we know the peer address. If it's a non-local host, start
// the Ping protocol to keep the port available. We can't rely on
// STKHost::isLAN(), since we might get a LAN connection even if
// the server itself accepts connections from anywhere.
if ( (!m_is_lan &&
m_peer_address.getIP() !=
NetworkConfig::get()->getMyAddress().getIP() ) ||
NetworkConfig::m_disable_lan )
{
m_current_protocol = new PingProtocol(m_peer_address,
/*time-between-ping*/2.0);
ProtocolManager::getInstance()->requestStart(m_current_protocol);
m_state = CONNECTING;
}
else
{
m_broadcast_count = 0;
// Make sure we trigger the broadcast operation next
m_time_last_broadcast = float(StkTime::getRealTime()-100.0f);
m_state = WAIT_FOR_LAN;
}
m_state = WAIT_FOR_CONNECTION;
m_timer = 0.0;
break;
}
case WAIT_FOR_LAN:
case WAIT_FOR_CONNECTION:
{
// Broadcast once per second
if (StkTime::getRealTime() < m_time_last_broadcast + 1.0f)
// Each 2 second for a ping or broadcast
if (StkTime::getRealTime() > m_timer + 2.0)
{
break;
}
m_time_last_broadcast = float(StkTime::getRealTime());
m_broadcast_count++;
if (m_broadcast_count > 100)
{
// Not much we can do about if we don't receive the client
// connection - it could have stopped, lost network, ...
// Terminate this protocol.
Log::error("ConnectToPeer", "Time out trying to connect to %s",
m_peer_address.toString().c_str());
requestTerminate();
}
// Otherwise we are in the same LAN (same public ip address).
// Just send a broadcast packet with the string aloha_stk inside,
// the client will know our ip address and will connect
TransportAddress broadcast_address;
if(NetworkConfig::get()->isWAN())
{
broadcast_address.setIP(-1); // 255.255.255.255
broadcast_address.setPort(m_peer_address.getPort());
}
else
m_timer = StkTime::getRealTime();
// Send a broadcast packet with the string aloha_stk inside,
// the client will know our ip address and will connect
// The wan remote should already start its ping message to us now
// so we can send packet directly to it.
TransportAddress broadcast_address;
broadcast_address.copy(m_peer_address);
BareNetworkString aloha(std::string("aloha_stk"));
STKHost::get()->sendRawPacket(aloha, broadcast_address);
Log::info("ConnectToPeer", "Broadcast aloha sent.");
StkTime::sleep(1);
broadcast_address.copy(m_peer_address);
broadcast_address.setIP(0x7f000001); // 127.0.0.1 (localhost)
broadcast_address.setPort(m_peer_address.getPort());
STKHost::get()->sendRawPacket(aloha, broadcast_address);
Log::info("ConnectToPeer", "Broadcast aloha to self.");
BareNetworkString aloha(std::string("aloha_stk"));
STKHost::get()->sendRawPacket(aloha, broadcast_address);
Log::info("ConnectToPeer", "Broadcast aloha sent.");
StkTime::sleep(1);
broadcast_address.setIP(0x7f000001); // 127.0.0.1 (localhost)
broadcast_address.setPort(m_peer_address.getPort());
STKHost::get()->sendRawPacket(aloha, broadcast_address);
Log::info("ConnectToPeer", "Broadcast aloha to self.");
// 20 seconds timeout
if (m_tried_connection++ > 10)
{
// Not much we can do about if we don't receive the client
// connection - it could have stopped, lost network, ...
// Terminate this protocol.
Log::error("ConnectToPeer", "Time out trying to connect to %s",
m_peer_address.toString().c_str());
requestTerminate();
}
}
break;
}
case CONNECTING: // waiting for the peer to connect
@ -196,14 +156,6 @@ void ConnectToPeer::asynchronousUpdate()
break;
case CONNECTED:
{
// If the ping protocol is there for NAT traversal terminate it.
// Ping is not running when connecting to a LAN peer.
if (m_current_protocol)
{
// Kill the ping protocol because we're connected
m_current_protocol->requestTerminate();
m_current_protocol = NULL;
}
m_state = DONE;
break;
}
@ -215,16 +167,3 @@ void ConnectToPeer::asynchronousUpdate()
break;
}
} // asynchronousUpdate
// ----------------------------------------------------------------------------
/** Callback from the GetPeerAddress protocol. It copies the received peer
* address so that it can be used in the next states of the connection
* protocol.
*/
void ConnectToPeer::callback(Protocol *protocol)
{
assert(m_state==RECEIVED_PEER_ADDRESS);
m_peer_address.copy( ((GetPeerAddress*)protocol)->getAddress() );
// Reactivate this protocol
requestUnpause();
} // callback

View File

@ -23,33 +23,38 @@
#include "network/transport_address.hpp"
#include "utils/cpp2011.hpp"
#include <chrono>
/** One instance of this is started for every peer who tries to
* connect to this server.
*/
class ConnectToPeer : public Protocol, public CallbackObject
class ConnectToPeer : public Protocol
{
protected:
TransportAddress m_peer_address;
uint32_t m_peer_id;
/** Pointer to the protocol which is monitored for state changes. */
Protocol *m_current_protocol;
/** Pointer to the protocol which is monitored for state changes, this
* need to be shared_ptr because we need to get the result from
* \ref GetPeerAddress, otherwise when it terminated the result will be
* gone. */
std::shared_ptr<Protocol> m_current_protocol;
/** True if this is a LAN connection. */
bool m_is_lan;
/** We might need to broadcast several times (in case the client is not
* ready in time). This keep track of broadcastst. */
float m_time_last_broadcast;
/** Timer use for tracking broadcast. */
double m_timer = 0.0;
int m_broadcast_count;
/** If greater than a certain value, terminate this protocol. */
unsigned m_tried_connection = 0;
enum STATE
{
NONE,
RECEIVED_PEER_ADDRESS,
WAIT_FOR_LAN,
WAIT_FOR_CONNECTION,
CONNECTING,
CONNECTED,
DONE,
@ -62,10 +67,9 @@ public:
virtual ~ConnectToPeer();
virtual bool notifyEventAsynchronous(Event* event) OVERRIDE;
virtual void setup() OVERRIDE;
virtual void setup() OVERRIDE {}
virtual void update(float dt) OVERRIDE {}
virtual void asynchronousUpdate() OVERRIDE;
virtual void callback(Protocol *protocol) OVERRIDE;
}; // class ConnectToPeer
#endif // CONNECT_TO_SERVER_HPP

View File

@ -18,14 +18,11 @@
#include "network/protocols/connect_to_server.hpp"
#include "config/player_manager.hpp"
#include "network/event.hpp"
#include "network/network_config.hpp"
#include "network/protocols/get_public_address.hpp"
#include "network/protocols/get_peer_address.hpp"
#include "network/protocols/hide_public_address.hpp"
#include "network/protocols/request_connection.hpp"
#include "network/protocols/ping_protocol.hpp"
#include "network/protocols/client_lobby.hpp"
#include "network/protocol_manager.hpp"
#include "network/servers_manager.hpp"
@ -34,12 +31,6 @@
#include "utils/time.hpp"
#include "utils/log.hpp"
#ifdef WIN32
# include <iphlpapi.h>
#else
#include <ifaddrs.h>
#endif
// ----------------------------------------------------------------------------
/** Connects to a server. This is the quick connect constructor, which
* will pick a server randomly.
@ -64,8 +55,8 @@ ConnectToServer::ConnectToServer(uint32_t server_id, uint32_t host_id)
m_server_id = server_id;
m_host_id = host_id;
m_quick_join = false;
const Server *server = ServersManager::get()->getServerByID(server_id);
m_server_address.copy(server->getAddress());
m_server = ServersManager::get()->getServerByID(m_server_id);
m_server_address.copy(m_server->getAddress());
setHandleConnections(true);
} // ConnectToServer(server, host)
@ -82,47 +73,30 @@ ConnectToServer::~ConnectToServer()
void ConnectToServer::setup()
{
Log::info("ConnectToServer", "SETUP");
m_current_protocol = NULL;
// In case of LAN we already have the server's and our ip address,
// so we can immediately start requesting a connection.
m_state = NetworkConfig::get()->isLAN() ? GOT_SERVER_ADDRESS : NONE;
m_current_protocol.reset();
// In case of LAN or client-server we already have the server's
// and our ip address, so we can immediately start requesting a connection.
m_state = (NetworkConfig::get()->isLAN() ||
STKHost::get()->isClientServer()) ?
GOT_SERVER_ADDRESS : SET_PUBLIC_ADDRESS;
} // setup
// ----------------------------------------------------------------------------
/** Sets the server transport address. This is used in case of LAN networking,
* when we do not query the stk server and instead have the address from the
* LAN server directly.
* \param address Address of server to connect to.
*/
void ConnectToServer::setServerAddress(const TransportAddress &address)
{
} // setServerAddress
// ----------------------------------------------------------------------------
void ConnectToServer::asynchronousUpdate()
{
switch(m_state)
switch(m_state.load())
{
case NONE:
case SET_PUBLIC_ADDRESS:
{
Log::info("ConnectToServer", "Protocol starting");
// This protocol will write the public address of this
// instance to STKHost.
m_current_protocol = new GetPublicAddress(this);
m_current_protocol->requestStart();
// This protocol will be unpaused in the callback from
// GetPublicAddress
requestPause();
m_state = GETTING_SELF_ADDRESS;
break;
STKHost::get()->setPublicAddress();
// Set to DONE will stop STKHost is not connected
m_state = STKHost::get()->getPublicAddress().isUnset() ?
DONE : REGISTER_SELF_ADDRESS;
}
case GETTING_SELF_ADDRESS:
break;
case REGISTER_SELF_ADDRESS:
{
delete m_current_protocol; // delete GetPublicAddress
m_current_protocol = NULL;
registerWithSTKServer(); // Register us with STK server
if (m_quick_join)
{
handleQuickConnect();
@ -139,139 +113,145 @@ void ConnectToServer::asynchronousUpdate()
case GOT_SERVER_ADDRESS:
{
assert(!m_quick_join);
delete m_current_protocol;
m_current_protocol = NULL;
Log::info("ConnectToServer", "Server's address known");
// we're in the same lan (same public ip address) !!
if (m_server_address.getIP() ==
NetworkConfig::get()->getMyAddress().getIP())
{
Log::info("ConnectToServer",
"Server appears to be in the same LAN.");
}
m_state = REQUESTING_CONNECTION;
m_current_protocol = new RequestConnection(m_server_id);
m_current_protocol->requestStart();
auto request_connection =
std::make_shared<RequestConnection>(m_server_id);
request_connection->requestStart();
m_current_protocol = request_connection;
// Reset timer for next usage
m_timer = 0.0;
break;
}
case REQUESTING_CONNECTION:
// In case of a LAN server, m_crrent_protocol is NULL
if (!m_current_protocol ||
m_current_protocol->getState() == PROTOCOL_STATE_TERMINATED)
if (!m_current_protocol.expired())
{
delete m_current_protocol;
m_current_protocol = NULL;
// Server knows we want to connect
Log::info("ConnectToServer", "Connection request made");
if (m_server_address.getIP() == 0 ||
m_server_address.getPort() == 0 )
{
// server data not correct, hide address and stop
m_state = HIDING_ADDRESS;
Log::error("ConnectToServer", "Server address is %s",
m_server_address.toString().c_str());
m_current_protocol = new HidePublicAddress();
m_current_protocol->requestStart();
return;
}
if( ( !NetworkConfig::m_disable_lan &&
m_server_address.getIP()
== NetworkConfig::get()->getMyAddress().getIP() ) ||
NetworkConfig::get()->isLAN() )
return;
}
// Server knows we want to connect
Log::info("ConnectToServer", "Connection request made");
if (m_server_address.isUnset())
{
// server data not correct, hide address and stop
m_state = HIDING_ADDRESS;
Log::error("ConnectToServer", "Server address is %s",
m_server_address.toString().c_str());
auto hide_address = std::make_shared<HidePublicAddress>();
hide_address->requestStart();
m_current_protocol = hide_address;
return;
}
if (m_tried_connection++ > 10)
{
Log::error("ConnectToServer", "Timeout waiting for aloha");
m_state = NetworkConfig::get()->isWAN() ?
HIDING_ADDRESS : DONE;
}
if ((!NetworkConfig::m_disable_lan &&
m_server_address.getIP() ==
STKHost::get()->getPublicAddress().getIP()) ||
(NetworkConfig::get()->isLAN() ||
STKHost::get()->isClientServer()))
{
// We're in the same lan (same public ip address).
// The state will change to CONNECTING
waitingAloha(false/*is_wan*/);
}
else
{
// Send a 1-byte datagram, the remote host can simply ignore
// this datagram, to keep the port open (2 second each)
if (StkTime::getRealTime() > m_timer + 2.0)
{
// We're in the same lan (same public ip address).
// The state will change to CONNECTING
handleSameLAN();
}
else
{
m_state = CONNECTING;
m_current_protocol = new PingProtocol(m_server_address, 2.0);
m_current_protocol->requestStart();
m_timer = StkTime::getRealTime();
BareNetworkString data;
data.addUInt8(0);
STKHost::get()->sendRawPacket(data, m_server_address);
}
waitingAloha(true/*is_wan*/);
}
break;
case CONNECTING: // waiting the server to answer our connection
{
// Every 5 seconds
if (StkTime::getRealTime() > m_timer + 5.0)
{
static double timer = 0;
if (StkTime::getRealTime() > timer+5.0) // every 5 seconds
m_timer = StkTime::getRealTime();
STKHost::get()->connect(m_server_address);
Log::info("ConnectToServer", "Trying to connect to %s",
m_server_address.toString().c_str());
if (m_tried_connection++ > 3)
{
STKHost::get()->connect(m_server_address);
timer = StkTime::getRealTime();
Log::info("ConnectToServer", "Trying to connect to %s",
m_server_address.toString().c_str());
Log::error("ConnectToServer", "Timeout connect to %s",
m_server_address.toString().c_str());
m_state = NetworkConfig::get()->isWAN() ?
HIDING_ADDRESS : DONE;
}
break;
}
break;
}
case CONNECTED:
{
Log::info("ConnectToServer", "Connected");
if(m_current_protocol)
{
// Kill the ping protocol because we're connected
m_current_protocol->requestTerminate();
}
delete m_current_protocol;
m_current_protocol = NULL;
// LAN networking does not use the stk server tables.
if(NetworkConfig::get()->isWAN())
if (NetworkConfig::get()->isWAN() &&
!STKHost::get()->isClientServer())
{
m_current_protocol = new HidePublicAddress();
m_current_protocol->requestStart();
auto hide_address = std::make_shared<HidePublicAddress>();
hide_address->requestStart();
m_current_protocol = hide_address;
}
m_state = HIDING_ADDRESS;
break;
}
case HIDING_ADDRESS:
// Wait till we have hidden our address
if (!m_current_protocol ||
m_current_protocol->getState() == PROTOCOL_STATE_TERMINATED)
if (!m_current_protocol.expired())
{
if(m_current_protocol)
{
delete m_current_protocol;
m_current_protocol = NULL;
Log::info("ConnectToServer", "Address hidden");
}
m_state = DONE;
// lobby room protocol if we're connected only
if(STKHost::get()->getPeers()[0]->isConnected())
{
ClientLobby *p =
LobbyProtocol::create<ClientLobby>();
p->setAddress(m_server_address);
p->requestStart();
}
return;
}
m_state = DONE;
break;
case DONE:
requestTerminate();
m_state = EXITING;
break;
case EXITING:
break;
}
} // asynchronousUpdate
// ----------------------------------------------------------------------------
/** Called when the GetPeerAddress protocol terminates.
*/
void ConnectToServer::callback(Protocol *protocol)
// ----------------------------------------------------------------------------
void ConnectToServer::update(float dt)
{
switch(m_state)
switch(m_state.load())
{
case GETTING_SELF_ADDRESS:
// The GetPublicAddress protocol stores our address in
// STKHost, so we only need to unpause this protocol
requestUnpause();
case DONE:
{
// lobby room protocol if we're connected only
if (STKHost::get()->getPeerCount() > 0 &&
STKHost::get()->getPeers()[0]->isConnected() &&
!m_server_address.isUnset())
{
// Let main thread create ClientLobby for better
// synchronization with GUI
auto cl = LobbyProtocol::create<ClientLobby>();
cl->setAddress(m_server_address);
cl->requestStart();
}
if (STKHost::get()->getPeerCount() == 0)
{
// Shutdown STKHost (go back to online menu too)
STKHost::get()->setErrorMessage(
_("Cannot connect to server %s.", m_server->getName()));
STKHost::get()->requestShutdown();
}
requestTerminate();
m_state = EXITING;
break;
}
default:
Log::error("ConnectToServer",
"Received unexpected callback while in state %d.",
m_state);
} // case m_state
} // callback
break;
}
} // update
// ----------------------------------------------------------------------------
/** Register this client with the STK server.
@ -280,14 +260,12 @@ void ConnectToServer::registerWithSTKServer()
{
// Our public address is now known, register details with
// STK server.
const TransportAddress& addr = NetworkConfig::get()->getMyAddress();
const TransportAddress& addr = STKHost::get()->getPublicAddress();
Online::XMLRequest *request = new Online::XMLRequest();
PlayerManager::setUserDetails(request, "set",
Online::API::SERVER_PATH);
NetworkConfig::get()->setUserDetails(request, "set");
request->addParameter("address", addr.getIP());
request->addParameter("port", addr.getPort());
request->addParameter("private_port",
NetworkConfig::get()->getClientPort());
request->addParameter("private_port", STKHost::get()->getPrivatePort());
Log::info("ConnectToServer", "Registering addr %s",
addr.toString().c_str());
@ -306,7 +284,10 @@ void ConnectToServer::registerWithSTKServer()
}
else
{
Log::error("ConnectToServer", "Failed to register address.");
irr::core::stringc error(request->getInfo().c_str());
Log::error("ConnectToServer", "Failed to register client address: %s",
error.c_str());
m_state = DONE;
}
delete request;
@ -318,12 +299,10 @@ void ConnectToServer::registerWithSTKServer()
void ConnectToServer::handleQuickConnect()
{
Online::XMLRequest *request = new Online::XMLRequest();
PlayerManager::setUserDetails(request, "quick-join",
Online::API::SERVER_PATH);
NetworkConfig::get()->setUserDetails(request, "quick-join");
request->executeNow();
const XMLNode * result = request->getXMLData();
delete request;
std::string success;
if(result->get("success", &success) && success=="yes")
@ -335,7 +314,7 @@ void ConnectToServer::handleQuickConnect()
uint16_t port;
// If we are using a LAN connection, we need the private (local) port
if (m_server_address.getIP() ==
NetworkConfig::get()->getMyAddress().getIP())
STKHost::get()->getPublicAddress().getIP())
{
result->get("private_port", &port);
}
@ -350,13 +329,15 @@ void ConnectToServer::handleQuickConnect()
{
Log::error("GetPeerAddress", "Failed to get address.");
}
delete request;
} // handleQuickConnect
// ----------------------------------------------------------------------------
/** Called when the server is on the same LAN. It uses broadcast to
* find and conntect to the server.
* find and conntect to the server. For WAN game, it makes sure server recieve
* request from stk addons first before continuing.
*/
void ConnectToServer::handleSameLAN()
void ConnectToServer::waitingAloha(bool is_wan)
{
// just send a broadcast packet, the client will know our
// ip address and will connect
@ -384,65 +365,20 @@ void ConnectToServer::handleSameLAN()
std::string aloha("aloha_stk");
if (received==aloha)
{
Log::info("ConnectToServer", "LAN Server found : %s",
Log::info("ConnectToServer", "Server found : %s",
sender.toString().c_str());
#ifndef WIN32
// just check if the ip is ours : if so,
// then just use localhost (127.0.0.1)
struct ifaddrs *ifap, *ifa;
struct sockaddr_in *sa;
getifaddrs(&ifap); // get the info
for (ifa = ifap; ifa; ifa = ifa->ifa_next)
if (!is_wan)
{
if (ifa->ifa_addr->sa_family == AF_INET)
{
sa = (struct sockaddr_in *) ifa->ifa_addr;
// This interface is ours
if (ntohl(sa->sin_addr.s_addr) == sender.getIP())
sender.setIP(0x7f000001); // 127.0.0.1
}
}
freeifaddrs(ifap);
#else
// Query the list of all IP addresses on the local host
// First call to GetIpAddrTable with 0 bytes buffer
// will return insufficient buffer error, and size
// will contain the number of bytes needed for all
// data. Repeat the process of querying the size
// using GetIpAddrTable in a while loop since it
// can happen that an interface comes online between
// the previous call to GetIpAddrTable and the next
// call.
MIB_IPADDRTABLE *table = NULL;
unsigned long size = 0;
int error = GetIpAddrTable(table, &size, 0);
// Also add a count to limit the while loop - in
// case that something strange is going on.
int count = 0;
while (error == ERROR_INSUFFICIENT_BUFFER && count < 10)
{
delete[] table; // deleting NULL is legal
table = (MIB_IPADDRTABLE*)new char[size];
error = GetIpAddrTable(table, &size, 0);
count++;
} // while insufficient buffer
for (unsigned int i = 0; i < table->dwNumEntries; i++)
{
unsigned int ip = ntohl(table->table[i].dwAddr);
if (sender.getIP() == ip) // this interface is ours
{
if (sender.isPublicAddressLAN())
sender.setIP(0x7f000001); // 127.0.0.1
break;
}
}
delete[] table;
#endif
m_server_address.copy(sender);
m_state = CONNECTING;
// Reset timer for next usage
m_timer = 0.0;
m_tried_connection = 0;
}
} // handleSameLAN
} // waitingAloha
// ----------------------------------------------------------------------------
@ -453,7 +389,6 @@ bool ConnectToServer::notifyEventAsynchronous(Event* event)
Log::info("ConnectToServer", "The Connect To Server protocol has "
"received an event notifying that he's connected to the peer.");
m_state = CONNECTED; // we received a message, we are connected
Server *server = ServersManager::get()->getJoinedServer();
}
return true;
} // notifyEventAsynchronous

View File

@ -22,37 +22,43 @@
#include "network/protocol.hpp"
#include "network/transport_address.hpp"
#include "utils/cpp2011.hpp"
#include <atomic>
#include <string>
class ConnectToServer : public Protocol, public CallbackObject
class Server;
class ConnectToServer : public Protocol
{
private:
double m_timer = 0.0;
TransportAddress m_server_address;
uint32_t m_server_id;
uint32_t m_host_id;
unsigned m_tried_connection = 0;
const Server* m_server = NULL;
/** Protocol currently being monitored. */
Protocol *m_current_protocol;
std::weak_ptr<Protocol> m_current_protocol;
bool m_quick_join;
/** State for finite state machine. */
enum
enum ConnectState : unsigned int
{
NONE,
GETTING_SELF_ADDRESS,
SET_PUBLIC_ADDRESS,
REGISTER_SELF_ADDRESS,
GOT_SERVER_ADDRESS,
REQUESTING_CONNECTION,
QUICK_JOIN,
CONNECTING,
CONNECTED,
HIDING_ADDRESS,
DONE,
EXITING
} m_state;
};
std::atomic<ConnectState> m_state;
void registerWithSTKServer();
void handleQuickConnect();
void handleSameLAN();
void waitingAloha(bool is_wan);
public:
ConnectToServer();
@ -62,9 +68,7 @@ public:
virtual bool notifyEventAsynchronous(Event* event) OVERRIDE;
virtual void setup() OVERRIDE;
virtual void asynchronousUpdate() OVERRIDE;
virtual void callback(Protocol *protocol) OVERRIDE;
virtual void update(float dt) OVERRIDE {}
void setServerAddress(const TransportAddress &address);
virtual void update(float dt) OVERRIDE;
}; // class ConnectToServer
#endif // CONNECT_TO_SERVER_HPP

View File

@ -1,136 +0,0 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2015 Supertuxkart-Team
//
// 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/protocols/controller_events_protocol.hpp"
#include "modes/world.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/controller.hpp"
#include "network/event.hpp"
#include "network/network_config.hpp"
#include "network/network_player_profile.hpp"
#include "network/game_setup.hpp"
#include "network/network_config.hpp"
#include "network/protocol_manager.hpp"
#include "network/stk_host.hpp"
#include "network/stk_peer.hpp"
#include "utils/log.hpp"
//-----------------------------------------------------------------------------
ControllerEventsProtocol::ControllerEventsProtocol()
: Protocol( PROTOCOL_CONTROLLER_EVENTS)
{
} // ControllerEventsProtocol
//-----------------------------------------------------------------------------
ControllerEventsProtocol::~ControllerEventsProtocol()
{
} // ~ControllerEventsProtocol
//-----------------------------------------------------------------------------
bool ControllerEventsProtocol::notifyEventAsynchronous(Event* event)
{
if(!checkDataSize(event, 13)) return true;
NetworkString &data = event->data();
float time = data.getFloat();
uint8_t client_index = -1;
while (data.size() >= 9)
{
uint8_t kart_id = data.getUInt8();
if (kart_id >=World::getWorld()->getNumKarts())
{
Log::warn("ControllerEventProtocol", "No valid kart id (%s).",
kart_id);
continue;
}
uint8_t serialized_1 = data.getUInt8();
uint8_t serialized_2 = data.getUInt8();
uint8_t serialized_3 = data.getUInt8();
PlayerAction action = (PlayerAction)(data.getUInt8());
int action_value = data.getUInt32();
Log::info("ControllerEventsProtocol", "KartID %d action %d value %d",
kart_id, action, action_value);
Controller *controller = World::getWorld()->getKart(kart_id)
->getController();
KartControl *controls = controller->getControls();
controls->setBrake( (serialized_1 & 0x40)!=0);
controls->setNitro( (serialized_1 & 0x20)!=0);
controls->setRescue( (serialized_1 & 0x10)!=0);
controls->setFire( (serialized_1 & 0x08)!=0);
controls->setLookBack((serialized_1 & 0x04)!=0);
controls->setSkidControl(KartControl::SkidControl(serialized_1 & 0x03));
controller->action(action, action_value);
}
if (data.size() > 0 )
{
Log::warn("ControllerEventProtocol",
"The data seems corrupted. Remains %d", data.size());
}
if (NetworkConfig::get()->isServer())
{
// Send update to all clients except the original sender.
STKHost::get()->sendPacketExcept(event->getPeer(),
&data, false);
} // if server
return true;
} // notifyEventAsynchronous
//-----------------------------------------------------------------------------
/** Called from the local kart controller when an action (like steering,
* acceleration, ...) was triggered. It compresses the current kart control
* state and sends a message with the new info to the server.
* \param controller The controller that triggered the action.
* \param action Which action was triggered.
* \param value New value for the given action.
*/
void ControllerEventsProtocol::controllerAction(Controller* controller,
PlayerAction action, int value)
{
assert(!NetworkConfig::get()->isServer());
KartControl* controls = controller->getControls();
uint8_t serialized_1 = 0;
serialized_1 |= (controls->getBrake()==true);
serialized_1 <<= 1;
serialized_1 |= (controls->getNitro()==true);
serialized_1 <<= 1;
serialized_1 |= (controls->getRescue()==true);
serialized_1 <<= 1;
serialized_1 |= (controls->getFire()==true);
serialized_1 <<= 1;
serialized_1 |= (controls->getLookBack()==true);
serialized_1 <<= 2;
serialized_1 += controls->getSkidControl();
uint8_t serialized_2 = (uint8_t)(controls->getAccel()*255.0);
uint8_t serialized_3 = (uint8_t)(controls->getSteer()*127.0);
NetworkString *ns = getNetworkString(13);
ns->addFloat(World::getWorld()->getTime());
ns->addUInt8(controller->getKart()->getWorldKartId());
ns->addUInt8(serialized_1).addUInt8(serialized_2).addUInt8(serialized_3);
ns->addUInt8((uint8_t)(action)).addUInt32(value);
sendToServer(ns, false); // send message to server
delete ns;
Log::info("ControllerEventsProtocol", "Action %d value %d", action, value);
} // controllerAction

View File

@ -1,47 +0,0 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2015 Supertuxkart-Team
//
// 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 CONTROLLER_EVENTS_PROTOCOL_HPP
#define CONTROLLER_EVENTS_PROTOCOL_HPP
#include "network/protocol.hpp"
#include "input/input.hpp"
#include "utils/cpp2011.hpp"
class Controller;
class STKPeer;
class ControllerEventsProtocol : public Protocol
{
public:
ControllerEventsProtocol();
virtual ~ControllerEventsProtocol();
virtual bool notifyEventAsynchronous(Event* event) OVERRIDE;
virtual void update(float dt) OVERRIDE {};
virtual void setup() OVERRIDE {};
virtual void asynchronousUpdate() OVERRIDE {}
void controllerAction(Controller* controller, PlayerAction action,
int value);
}; // class ControllerEventsProtocol
#endif // CONTROLLER_EVENTS_PROTOCOL_HPP

View File

@ -0,0 +1,327 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2015 Supertuxkart-Team
//
// 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/protocols/game_protocol.hpp"
#include "modes/world.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/player_controller.hpp"
#include "network/event.hpp"
#include "network/network_config.hpp"
#include "network/network_player_profile.hpp"
#include "network/game_setup.hpp"
#include "network/network_config.hpp"
#include "network/network_string.hpp"
#include "network/protocol_manager.hpp"
#include "network/rewind_manager.hpp"
#include "network/stk_host.hpp"
#include "network/stk_peer.hpp"
#include "utils/log.hpp"
// ============================================================================
std::weak_ptr<GameProtocol> GameProtocol::m_game_protocol;
// ============================================================================
std::shared_ptr<GameProtocol> GameProtocol::createInstance()
{
if (!emptyInstance())
{
Log::fatal("GameProtocol", "Create only 1 instance of GameProtocol!");
return NULL;
}
auto gm = std::make_shared<GameProtocol>();
m_game_protocol = gm;
return gm;
} // createInstance
//-----------------------------------------------------------------------------
/** Constructor. Allocates the buffer for events to send to the server. */
GameProtocol::GameProtocol()
: Protocol( PROTOCOL_CONTROLLER_EVENTS)
{
m_data_to_send = getNetworkString();
} // GameProtocol
//-----------------------------------------------------------------------------
GameProtocol::~GameProtocol()
{
delete m_data_to_send;
} // ~GameProtocol
//-----------------------------------------------------------------------------
/** Synchronous update - will send all commands collected during the last
* frame (and could optional only send messages every N frames).
*/
void GameProtocol::update(float dt)
{
if (m_all_actions.size() == 0) return; // nothing to do
// Clear left-over data from previous frame. This way the network
// string will increase till it reaches maximum size necessary
m_data_to_send->clear();
m_data_to_send->addUInt8(GP_CONTROLLER_ACTION)
.addUInt8(uint8_t(m_all_actions.size()));
// Add all actions
for (auto a : m_all_actions)
{
m_data_to_send->addFloat(a.m_time);
m_data_to_send->addUInt8(a.m_kart_id);
m_data_to_send->addUInt8((uint8_t)(a.m_action)).addUInt32(a.m_value)
.addUInt32(a.m_value_l).addUInt32(a.m_value_r);
} // for a in m_all_actions
// FIXME: for now send reliable
sendToServer(m_data_to_send, /*reliable*/ true);
m_all_actions.clear();
} // update
//-----------------------------------------------------------------------------
/** Called when a message from a remote GameProtocol is received.
*/
bool GameProtocol::notifyEventAsynchronous(Event* event)
{
if(!checkDataSize(event, 1)) return true;
NetworkString &data = event->data();
uint8_t message_type = data.getUInt8();
switch (message_type)
{
case GP_CONTROLLER_ACTION: handleControllerAction(event); break;
case GP_STATE: handleState(event); break;
case GP_ADJUST_TIME: handleAdjustTime(event); break;
default: Log::error("GameProtocol",
"Received unknown message type %d - ignored.",
message_type); break;
} // switch message_type
return true;
} // notifyEventAsynchronous
//-----------------------------------------------------------------------------
/** Called from the local kart controller when an action (like steering,
* acceleration, ...) was triggered. It sends a message with the new info
* to the server and informs the rewind manager to store the event.
* \param Kart id that triggered the action.
* \param action Which action was triggered.
* \param value New value for the given action.
*/
void GameProtocol::controllerAction(int kart_id, PlayerAction action,
int value, int val_l, int val_r)
{
// Store the action in the list of actions that will be sent to the
// server next.
assert(NetworkConfig::get()->isClient());
Action a;
a.m_kart_id = kart_id;
a.m_action = action;
a.m_value = value;
a.m_value_l = val_l;
a.m_value_r = val_r;
a.m_time = World::getWorld()->getTime();
m_all_actions.push_back(a);
// Store the event in the rewind manager, which is responsible
// for freeing the allocated memory
BareNetworkString *s = new BareNetworkString(4);
s->addUInt8(kart_id).addUInt8(action).addUInt32(value)
.addUInt32(val_l).addUInt32(val_r);
RewindManager::get()->addEvent(this, s, /*confirmed*/true,
World::getWorld()->getTime() );
Log::info("GameProtocol", "Action at %f: %d value %d",
World::getWorld()->getTime(), action,
action==PlayerAction::PA_STEER_RIGHT ? -value : value);
} // controllerAction
// ----------------------------------------------------------------------------
/** Called when a controller event is received - either on the server from
* a client, or on a client from the server. It sorts the event into the
* RewindManager's network event queue. The server will also send this
* event immediately to all clients (except to the original sender).
*/
void GameProtocol::handleControllerAction(Event *event)
{
NetworkString &data = event->data();
uint8_t count = data.getUInt8();
bool will_trigger_rewind = false;
float rewind_delta = 0.0f;
for (unsigned int i = 0; i < count; i++)
{
float time = data.getFloat();
// Since this is running in a thread, it might be called during
// a rewind, i.e. with an incorrect world time. So the event
// time needs to be compared with the World time independent
// of any rewinding.
if (time < RewindManager::get()->getNotRewoundWorldTime() &&
!will_trigger_rewind )
{
will_trigger_rewind = true;
rewind_delta = time - RewindManager::get()->getNotRewoundWorldTime();
}
uint8_t kart_id = data.getUInt8();
assert(kart_id < World::getWorld()->getNumKarts());
PlayerAction action = (PlayerAction)(data.getUInt8());
int value = data.getUInt32();
int value_l = data.getUInt32();
int value_r = data.getUInt32();
Log::info("GameProtocol", "Action at %f: %d %d %d %d %d",
time, kart_id, action, value, value_l, value_r);
BareNetworkString *s = new BareNetworkString(3);
s->addUInt8(kart_id).addUInt8(action).addUInt32(value)
.addUInt32(value_l).addUInt32(value_r);
RewindManager::get()->addNetworkEvent(this, s, time);
}
if (data.size() > 0)
{
Log::warn("GameProtocol",
"Received invalid controller data - remains %d",data.size());
}
if (NetworkConfig::get()->isServer())
{
// Send update to all clients except the original sender.
STKHost::get()->sendPacketExcept(event->getPeer(),
&data, false);
if (will_trigger_rewind)
{
Log::info("GameProtocol",
"At %f %f %f requesting time adjust of %f for host %d",
World::getWorld()->getTime(), StkTime::getRealTime(),
RewindManager::get()->getNotRewoundWorldTime(),
rewind_delta, event->getPeer()->getHostId());
// This message from a client triggered a rewind in the server.
// To avoid this, signal to the client that it should slow down.
adjustTimeForClient(event->getPeer(), rewind_delta);
}
} // if server
} // handleControllerAction
// ----------------------------------------------------------------------------
/** The server might request that a client adjusts its world clock (in order to
* reduce rewinds). This function sends a a (unreliable) message to the
* client.
* \param peer The peer that triggered the rewind.
* \param t Time that the peer needs to slowdown (<0) or sped up(>0).
*/
void GameProtocol::adjustTimeForClient(STKPeer *peer, float t)
{
assert(NetworkConfig::get()->isServer());
NetworkString *ns = getNetworkString(5);
ns->addUInt8(GP_ADJUST_TIME).addFloat(t);
// This message can be send unreliable, it's not critical if it doesn't
// get delivered, the server will request again later anyway.
peer->sendPacket(ns, /*reliable*/false);
delete ns;
} // adjustTimeForClient
// ----------------------------------------------------------------------------
/** Called on a client when the server requests an adjustment of this client's
* world clock time (in order to reduce rewinds).
*/
void GameProtocol::handleAdjustTime(Event *event)
{
float t = event->data().getFloat();
World::getWorld()->setAdjustTime(t);
} // handleAdjustTime
// ----------------------------------------------------------------------------
/** Called by the server before assembling a new message containing the full
* state of the race to be sent to a client.
*/
void GameProtocol::startNewState()
{
assert(NetworkConfig::get()->isServer());
m_data_to_send->clear();
m_data_to_send->addUInt8(GP_STATE).addFloat(World::getWorld()->getTime());
Log::info("GameProtocol", "Sending new state at %f.",
World::getWorld()->getTime());
} // startNewState
// ----------------------------------------------------------------------------
/** Called by a server to add data to the current state.
*/
void GameProtocol::addState(BareNetworkString *buffer)
{
assert(NetworkConfig::get()->isServer());
m_data_to_send->addUInt16(buffer->size());
(*m_data_to_send) += *buffer;
} // addState
// ----------------------------------------------------------------------------
/** Called when the last state information has been added and the message
* can be sent to the clients.
*/
void GameProtocol::sendState()
{
assert(NetworkConfig::get()->isServer());
sendMessageToPeersChangingToken(m_data_to_send, /*reliable*/true);
} // sendState
// ----------------------------------------------------------------------------
/** Called when a new full state is received form the server.
*/
void GameProtocol::handleState(Event *event)
{
// Ignore events arriving when client has already exited
if (!World::getWorld())
return;
assert(NetworkConfig::get()->isClient());
NetworkString &data = event->data();
float time = data.getFloat();
Log::info("GameProtocol", "Received at %f state from %f",
World::getWorld()->getTime(), time);
int index = 0;
while (data.size() > 0)
{
uint16_t count = data.getUInt16();
BareNetworkString *state = new BareNetworkString(data.getCurrentData(),
count);
data.skip(count);
RewindManager::get()->addNetworkState(index, state, time);
index++;
} // while data.size()>0
} // handleState
// ----------------------------------------------------------------------------
/** Called from the RewindManager when rolling back.
* \param buffer Pointer to the saved state information.
*/
void GameProtocol::undo(BareNetworkString *buffer)
{
} // undo
// ----------------------------------------------------------------------------
/** Called from the RewindManager after a rollback to replay the stored
* events.
* \param buffer Pointer to the saved state information.
*/
void GameProtocol::rewind(BareNetworkString *buffer)
{
int kart_id = buffer->getUInt8();
PlayerAction action = PlayerAction(buffer->getUInt8());
int value = buffer->getUInt32();
int value_l = buffer->getUInt32();
int value_r = buffer->getUInt32();
Controller *c = World::getWorld()->getKart(kart_id)->getController();
PlayerController *pc = dynamic_cast<PlayerController*>(c);
// FIXME this can be endcontroller when finishing the race
//assert(pc);
if(pc)
pc->actionFromNetwork(action, value, value_l, value_r);
} // rewind

View File

@ -0,0 +1,107 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2015 Supertuxkart-Team
//
// 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 GAME_PROTOCOL_HPP
#define GAME_PROTOCOL_HPP
#include "network/event_rewinder.hpp"
#include "network/protocol.hpp"
#include "input/input.hpp" // for PlayerAction
#include "utils/cpp2011.hpp"
#include "utils/singleton.hpp"
#include <vector>
class BareNetworkString;
class NetworkString;
class STKPeer;
class GameProtocol : public Protocol
, public EventRewinder
{
private:
/** The type of game events to be forwarded to the server. */
enum { GP_CONTROLLER_ACTION,
GP_STATE,
GP_ADJUST_TIME
};
/** A network string that collects all information from the server to be sent
* next. */
NetworkString *m_data_to_send;
/** The server might request that the world clock of a client is adjusted
* to reduce number of rollbacks. */
std::vector<int8_t> m_adjust_time;
// Dummy data structure to save all kart actions.
struct Action
{
float m_time;
int m_kart_id;
PlayerAction m_action;
int m_value;
int m_value_l;
int m_value_r;
}; // struct Action
// List of all kart actions to send to the server
std::vector<Action> m_all_actions;
void handleControllerAction(Event *event);
void handleState(Event *event);
void handleAdjustTime(Event *event);
static std::weak_ptr<GameProtocol> m_game_protocol;
public:
GameProtocol();
virtual ~GameProtocol();
virtual bool notifyEventAsynchronous(Event* event) OVERRIDE;
virtual void update(float dt) OVERRIDE;
void controllerAction(int kart_id, PlayerAction action,
int value, int val_l, int val_r);
void startNewState();
void addState(BareNetworkString *buffer);
void sendState();
void adjustTimeForClient(STKPeer *peer, float t);
virtual void undo(BareNetworkString *buffer) OVERRIDE;
virtual void rewind(BareNetworkString *buffer) OVERRIDE;
// ------------------------------------------------------------------------
virtual void setup() OVERRIDE {};
// ------------------------------------------------------------------------
virtual void asynchronousUpdate() OVERRIDE {}
// ------------------------------------------------------------------------
static std::shared_ptr<GameProtocol> createInstance();
// ------------------------------------------------------------------------
static bool emptyInstance()
{
return m_game_protocol.expired();
} // emptyInstance
// ------------------------------------------------------------------------
static std::shared_ptr<GameProtocol> lock()
{
return m_game_protocol.lock();
} // lock
}; // class GameProtocol
#endif // GAME_PROTOCOL_HPP

View File

@ -18,16 +18,15 @@
#include "network/protocols/get_peer_address.hpp"
#include "config/player_manager.hpp"
#include "config/user_config.hpp"
#include "network/network_config.hpp"
#include "network/protocol_manager.hpp"
#include "network/stk_host.hpp"
#include "online/request_manager.hpp"
#include "online/xml_request.hpp"
#include "utils/log.hpp"
GetPeerAddress::GetPeerAddress(uint32_t peer_id,
CallbackObject* callback_object)
: Protocol(PROTOCOL_SILENT, callback_object)
GetPeerAddress::GetPeerAddress(uint32_t peer_id)
: Protocol(PROTOCOL_SILENT, NULL)
{
m_peer_id = peer_id;
} // GetPeerAddress
@ -41,10 +40,8 @@ GetPeerAddress::~GetPeerAddress()
void GetPeerAddress::setup()
{
m_address.clear();
m_request = new Online::XMLRequest();
PlayerManager::setUserDetails(m_request, "get",
Online::API::SERVER_PATH);
NetworkConfig::get()->setUserDetails(m_request, "get");
m_request->addParameter("peer_id", m_peer_id);
Online::RequestManager::get()->addRequest(m_request);
@ -65,7 +62,7 @@ void GetPeerAddress::asynchronousUpdate()
m_address.setIP(ip);
uint16_t port;
uint32_t my_ip = NetworkConfig::get()->getMyAddress().getIP();
uint32_t my_ip = STKHost::get()->getPublicAddress().getIP();
if (m_address.getIP() == my_ip && !NetworkConfig::m_disable_lan)
result->get("private_port", &port);
else
@ -84,9 +81,3 @@ void GetPeerAddress::asynchronousUpdate()
m_request = NULL;
}
} // asynchronousUpdate
// ----------------------------------------------------------------------------
void GetPeerAddress::setPeerID(uint32_t peer_id)
{
m_peer_id = peer_id;
} // setPeerID

View File

@ -35,13 +35,12 @@ private:
* to get the result. */
TransportAddress m_address;
public:
GetPeerAddress(uint32_t peer_id, CallbackObject* callback_object);
GetPeerAddress(uint32_t peer_id);
virtual ~GetPeerAddress();
virtual void setup() OVERRIDE;
virtual void asynchronousUpdate() OVERRIDE;
void setPeerID(uint32_t m_peer_id);
void setPeerID(uint32_t peer_id) { m_peer_id = peer_id; }
// ------------------------------------------------------------------------
/** Returns the address found. */
const TransportAddress &getAddress() const { return m_address; }

View File

@ -1,274 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2013-2015 SuperTuxKart-Team
//
// 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/protocols/get_public_address.hpp"
#include "config/user_config.hpp"
#include "network/network.hpp"
#include "network/network_config.hpp"
#include "network/network_string.hpp"
#include "network/protocols/connect_to_server.hpp"
#include "network/stk_host.hpp"
#include "utils/log.hpp"
#include "utils/string_utils.hpp"
#include <assert.h>
#include <string>
#ifdef __MINGW32__
# undef _WIN32_WINNT
# define _WIN32_WINNT 0x501
#endif
#ifdef WIN32
# include <winsock2.h>
# include <ws2tcpip.h>
#else
# include <netdb.h>
# include <sys/socket.h>
#endif
#include <sys/types.h>
// make the linker happy
const uint32_t GetPublicAddress::m_stun_magic_cookie = 0x2112A442;
TransportAddress GetPublicAddress::m_my_address(0, 0);
void GetPublicAddress::setMyIPAddress(const std::string &s)
{
std::vector<std::string> l = StringUtils::split(s, ':');
if (l.size() != 2)
{
Log::fatal("Invalid IP address '%s'.", s.c_str());
}
std::vector<std::string> ip = StringUtils::split(l[0], '.');
if (ip.size() != 4)
{
Log::fatal("Invalid IP address '%s'.", s.c_str());
}
uint32_t u = 0;
for (unsigned int i = 0; i < 4; i++)
{
int k;
StringUtils::fromString(ip[i], k);
u = (u << 8) + k;
}
m_my_address.setIP(u);
int p;
StringUtils::fromString(l[1], p);
m_my_address.setPort(p);
} // setMyIPAddress
// ============================================================================
GetPublicAddress::GetPublicAddress(CallbackObject *callback)
: Protocol(PROTOCOL_SILENT, callback)
{
m_state = NOTHING_DONE;
} // GetPublicAddress
// ----------------------------------------------------------------------------
/** Creates a STUN request and sends it to a random STUN server selected from
* the list stored in the config file. See
* https://tools.ietf.org/html/rfc5389#section-6
* for details on the message structure.
* The request is send through m_transaction_host, from which the answer
* will be retrieved by parseStunResponse()
*/
void GetPublicAddress::createStunRequest()
{
// Pick a random stun server
std::vector<std::string> stun_servers = UserConfigParams::m_stun_servers;
const char* server_name = stun_servers[rand() % stun_servers.size()].c_str();
Log::debug("GetPublicAddress", "Using STUN server %s", server_name);
struct addrinfo hints, *res;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
hints.ai_socktype = SOCK_STREAM;
// Resolve the stun server name so we can send it a STUN request
int status = getaddrinfo(server_name, NULL, &hints, &res);
if (status != 0)
{
Log::error("GetPublicAddress", "Error in getaddrinfo: %s",
gai_strerror(status));
return;
}
// documentation says it points to "one or more addrinfo structures"
assert(res != NULL);
struct sockaddr_in* current_interface = (struct sockaddr_in*)(res->ai_addr);
m_stun_server_ip = ntohl(current_interface->sin_addr.s_addr);
// Create a new socket for the stun server.
ENetAddress addr;
addr.host = STKHost::HOST_ANY;
addr.port = STKHost::PORT_ANY;
m_transaction_host = new Network(1, 1, 0, 0, &addr);
// Assemble the message for the stun server
BareNetworkString s(20);
// bytes 0-1: the type of the message
// bytes 2-3: message length added to header (attributes)
uint16_t message_type = 0x0001; // binding request
uint16_t message_length = 0x0000;
s.addUInt16(message_type).addUInt16(message_length)
.addUInt32(0x2112A442);
// bytes 8-19: the transaction id
for (int i = 0; i < 12; i++)
{
uint8_t random_byte = rand() % 256;
s.addUInt8(random_byte);
m_stun_tansaction_id[i] = random_byte;
}
m_transaction_host->sendRawPacket(s,
TransportAddress(m_stun_server_ip,
m_stun_server_port) );
freeaddrinfo(res);
m_state = STUN_REQUEST_SENT;
} // createStunRequest
// ----------------------------------------------------------------------------
/**
* Gets the response from the STUN server, checks it for its validity and
* then parses the answer into address and port
* \return "" if the address could be parsed or an error message
*/
std::string GetPublicAddress::parseStunResponse()
{
TransportAddress sender;
const int LEN = 2048;
char buffer[LEN];
int len = m_transaction_host->receiveRawPacket(buffer, LEN, &sender, 2000);
if(sender.getIP()!=m_stun_server_ip)
{
TransportAddress stun(m_stun_server_ip, m_stun_server_port);
Log::warn("GetPublicAddress",
"Received stun response from %s instead of %s.",
sender.toString().c_str(), stun.toString().c_str());
}
if (len<0)
return "STUN response contains no data at all";
// Convert to network string.
BareNetworkString datas(buffer, len);
// check that the stun response is a response, contains the magic cookie
// and the transaction ID
if (datas.getUInt16() != 0x0101)
return "STUN response doesn't contain the magic cookie";
int message_size = datas.getUInt16();
if (datas.getUInt32() != m_stun_magic_cookie)
{
return "STUN response doesn't contain the magic cookie";
}
for (int i = 0; i < 12; i++)
{
if (datas.getUInt8() != m_stun_tansaction_id[i])
return "STUN response doesn't contain the transaction ID";
}
Log::debug("GetPublicAddress",
"The STUN server responded with a valid answer");
// The stun message is valid, so we parse it now:
if (message_size == 0)
return "STUN response does not contain any information.";
if (message_size < 4) // cannot even read the size
return "STUN response is too short.";
// Those are the port and the address to be detected
int pos = 20;
while (true)
{
int type = datas.getUInt16();
int size = datas.getUInt16();
if (type == 0 || type == 1)
{
assert(size == 8);
datas.getUInt8(); // skip 1 byte
assert(datas.getUInt8() == 0x01); // Family IPv4 only
uint16_t port = datas.getUInt16();
uint32_t ip = datas.getUInt32();
TransportAddress address(ip, port);
// finished parsing, we know our public transport address
Log::debug("GetPublicAddress",
"The public address has been found: %s",
address.toString().c_str());
NetworkConfig::get()->setMyAddress(address);
break;
} // type = 0 or 1
datas.skip(4 + size);
message_size -= 4 + size;
if (message_size == 0)
return "STUN response is invalid.";
if (message_size < 4) // cannot even read the size
return "STUN response is invalid.";
} // while true
return "";
} // parseStunResponse
// ----------------------------------------------------------------------------
/** Detects public IP-address and port by first sending a request to a randomly
* selected STUN server and then parsing and validating the response */
void GetPublicAddress::asynchronousUpdate()
{
// If the user has specified an address, use it instead of the stun protocol.
if (m_my_address.getIP() != 0 && m_my_address.getPort() != 0)
{
NetworkConfig::get()->setMyAddress(m_my_address);
m_state = EXITING;
requestTerminate();
}
//#define LAN_TEST
#ifdef LAN_TEST
TransportAddress address(0x7f000001, 4);
NetworkConfig::get()->setMyAddress(address);
m_state = EXITING;
requestTerminate();
return;
#endif
if (m_state == NOTHING_DONE)
{
createStunRequest();
}
if (m_state == STUN_REQUEST_SENT)
{
std::string message = parseStunResponse();
delete m_transaction_host;
if (message != "")
{
Log::warn("GetPublicAddress", "%s", message.c_str());
m_state = NOTHING_DONE; // try again
}
else
{
// The address and the port are known, so the connection can be closed
m_state = EXITING;
requestTerminate();
}
}
} // asynchronousUpdate

View File

@ -1,73 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2013-2015 SuperTuxKart-Team
//
// 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 GET_PUBLIC_ADDRESS_HPP
#define GET_PUBLIC_ADDRESS_HPP
#include "network/protocol.hpp"
#include "network/transport_address.hpp"
#include "utils/cpp2011.hpp"
#include <string>
class Network;
class GetPublicAddress : public Protocol
{
private:
void createStunRequest();
std::string parseStunResponse();
// Constants
static const uint32_t m_stun_magic_cookie;
static const int m_stun_server_port = 3478;
/** The user can specify its own IP address to make the use of stun
* unnecessary (though that means that the user has to take care of
* opening the firewall). */
static TransportAddress m_my_address;
enum State
{
NOTHING_DONE,
STUN_REQUEST_SENT,
EXITING
} m_state;
uint8_t m_stun_tansaction_id[12];
uint32_t m_stun_server_ip;
Network* m_transaction_host;
public:
static void setMyIPAddress(const std::string &s);
GetPublicAddress(CallbackObject *callback = NULL);
virtual ~GetPublicAddress() {}
virtual void asynchronousUpdate() OVERRIDE;
// ------------------------------------------------------------------------
virtual void update(float dt) OVERRIDE {}
// ------------------------------------------------------------------------
virtual bool notifyEvent(Event* event) OVERRIDE { return true; }
// ------------------------------------------------------------------------
virtual bool notifyEventAsynchronous(Event* event) OVERRIDE { return true; }
// ------------------------------------------------------------------------
virtual void setup() { m_state = NOTHING_DONE; }
// ------------------------------------------------------------------------
}; // class GetPublicAddress
#endif // GET_PUBLIC_ADDRESS_HPP

View File

@ -18,10 +18,9 @@
#include "network/protocols/hide_public_address.hpp"
#include "config/player_manager.hpp"
#include "config/user_config.hpp"
#include "network/protocol_manager.hpp"
#include "network/network_config.hpp"
#include "online/request_manager.hpp"
#include "online/xml_request.hpp"
#include "utils/log.hpp"
HidePublicAddress::HidePublicAddress() : Protocol(PROTOCOL_SILENT)
@ -42,8 +41,7 @@ void HidePublicAddress::asynchronousUpdate()
if (m_state == NONE)
{
m_request = new Online::XMLRequest();
PlayerManager::setUserDetails(m_request, "unset", Online::API::SERVER_PATH);
NetworkConfig::get()->setUserDetails(m_request, "unset");
Online::RequestManager::get()->addRequest(m_request);
m_state = REQUEST_PENDING;
}
@ -56,7 +54,7 @@ void HidePublicAddress::asynchronousUpdate()
{
if(rec_success == "yes")
{
Log::debug("HidePublicAddress", "Address hidden successfully.");
Log::info("HidePublicAddress", "Address hidden successfully.");
}
else
{

View File

@ -1,147 +0,0 @@
#include "network/protocols/kart_update_protocol.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/controller.hpp"
#include "modes/world.hpp"
#include "network/event.hpp"
#include "network/network_config.hpp"
#include "network/protocol_manager.hpp"
#include "utils/time.hpp"
KartUpdateProtocol::KartUpdateProtocol() : Protocol(PROTOCOL_KART_UPDATE)
{
} // KartUpdateProtocol
// ----------------------------------------------------------------------------
KartUpdateProtocol::~KartUpdateProtocol()
{
} // ~KartUpdateProtocol
// ----------------------------------------------------------------------------
void KartUpdateProtocol::setup()
{
// Allocate arrays to store one position and rotation for each kart
// (which is the update information from the server to the client).
m_next_positions.resize(World::getWorld()->getNumKarts());
m_next_quaternions.resize(World::getWorld()->getNumKarts());
// This flag keeps track if valid data for an update is in
// the arrays
m_was_updated = false;
m_previous_time = 0;
} // setup
// ----------------------------------------------------------------------------
/** Store the update events in the queue. Since the events are handled in the
* synchronous notify function, there is no lock necessary to
*/
bool KartUpdateProtocol::notifyEvent(Event* event)
{
// It might be possible that we still receive messages after
// the game was exited, so make sure we still have a world.
if (event->getType() != EVENT_TYPE_MESSAGE || !World::getWorld())
return true;
NetworkString &ns = event->data();
if (ns.size() < 33)
{
Log::info("KartUpdateProtocol", "Message too short.");
return true;
}
float time = ns.getFloat();
while(ns.size() >= 29)
{
uint8_t kart_id = ns.getUInt8();
Vec3 xyz = ns.getVec3();
btQuaternion quat = ns.getQuat();
m_next_positions [kart_id] = xyz;
m_next_quaternions[kart_id] = quat;
} // while ns.size()>29
// Set the flag that a new update was received
m_was_updated = true;
return true;
} // notifyEvent
// ----------------------------------------------------------------------------
/** Sends regular update events from the server to all clients and from the
* clients to the server (FIXME - is that actually necessary??)
* Then it applies all update events that have been received in notifyEvent.
* This two-part implementation means that if the server should send two
* or more updates before this client handles them, only the last one will
* actually be handled (i.e. outdated kart position updates are discarded).
*/
void KartUpdateProtocol::update(float dt)
{
if (!World::getWorld())
return;
double current_time = StkTime::getRealTime();
if (current_time > m_previous_time + 0.1) // 10 updates per second
{
m_previous_time = current_time;
if (NetworkConfig::get()->isServer())
{
World *world = World::getWorld();
NetworkString *ns = getNetworkString(4+world->getNumKarts()*29);
ns->setSynchronous(true);
ns->addFloat( world->getTime() );
for (unsigned int i = 0; i < world->getNumKarts(); i++)
{
AbstractKart* kart = world->getKart(i);
Vec3 xyz = kart->getXYZ();
ns->addUInt8( kart->getWorldKartId());
ns->add(xyz).add(kart->getRotation());
Log::verbose("KartUpdateProtocol",
"Sending %d's positions %f %f %f",
kart->getWorldKartId(), xyz[0], xyz[1], xyz[2]);
}
sendMessageToPeersChangingToken(ns, /*reliable*/false);
delete ns;
}
else
{
NetworkString *ns =
getNetworkString(4+29*race_manager->getNumLocalPlayers());
ns->setSynchronous(true);
ns->addFloat(World::getWorld()->getTime());
for(unsigned int i=0; i<race_manager->getNumLocalPlayers(); i++)
{
AbstractKart *kart = World::getWorld()->getLocalPlayerKart(i);
const Vec3 &xyz = kart->getXYZ();
ns->addUInt8(kart->getWorldKartId());
ns->add(xyz).add(kart->getRotation());
Log::verbose("KartUpdateProtocol",
"Sending %d's positions %f %f %f",
kart->getWorldKartId(), xyz[0], xyz[1], xyz[2]);
}
sendToServer(ns, /*reliable*/false);
delete ns;
} // if server
} // if (current_time > time + 0.1)
// Now handle all update events that have been received.
// There is no lock necessary, since receiving new positions is done in
// notifyEvent, which is called from the same thread that calls this
// function.
if(m_was_updated)
{
for (unsigned id = 0; id < m_next_positions.size(); id++)
{
AbstractKart *kart = World::getWorld()->getKart(id);
if (!kart->getController()->isLocalPlayerController())
{
btTransform transform = kart->getBody()
->getInterpolationWorldTransform();
transform.setOrigin(m_next_positions[id]);
transform.setRotation(m_next_quaternions[id]);
kart->getBody()->setCenterOfMassTransform(transform);
Log::verbose("KartUpdateProtocol", "Update kart %i pos",
id);
} // if not local player
} // for id < num_karts
m_was_updated = false; // mark that all updates were applied
} // if m_was_updated
} // update

View File

@ -1,43 +0,0 @@
#ifndef KART_UPDATE_PROTOCOL_HPP
#define KART_UPDATE_PROTOCOL_HPP
#include "network/protocol.hpp"
#include "utils/cpp2011.hpp"
#include "utils/vec3.hpp"
#include "LinearMath/btQuaternion.h"
#include <vector>
#include "pthread.h"
class AbstractKart;
class KartUpdateProtocol : public Protocol
{
private:
/** Stores the last updated position for a kart. */
std::vector<Vec3> m_next_positions;
/** Stores the last updated rotation for a kart. */
std::vector<btQuaternion> m_next_quaternions;
/** True if a new update for the kart positions was received. */
bool m_was_updated;
/** Time the last kart update was sent. Used to send updates with
* a fixed frequency. */
double m_previous_time;
public:
KartUpdateProtocol();
virtual ~KartUpdateProtocol();
virtual bool notifyEvent(Event* event) OVERRIDE;
virtual void setup() OVERRIDE;
virtual void update(float dt) OVERRIDE;
virtual void asynchronousUpdate() OVERRIDE {};
}; // KartUpdateProtocol
#endif // KART_UPDATE_PROTOCOL_HPP

View File

@ -24,16 +24,16 @@
#include "modes/world.hpp"
#include "network/network_player_profile.hpp"
#include "network/protocol_manager.hpp"
#include "network/protocols/controller_events_protocol.hpp"
#include "network/protocols/game_protocol.hpp"
#include "network/protocols/game_events_protocol.hpp"
#include "network/protocols/kart_update_protocol.hpp"
#include "network/protocols/latency_protocol.hpp"
#include "network/race_event_manager.hpp"
#include "network/rewind_manager.hpp"
#include "network/stk_host.hpp"
#include "race/race_manager.hpp"
#include "states_screens/state_manager.hpp"
LobbyProtocol *LobbyProtocol::m_lobby = NULL;
std::weak_ptr<LobbyProtocol> LobbyProtocol::m_lobby;
LobbyProtocol::LobbyProtocol(CallbackObject* callback_object)
: Protocol(PROTOCOL_LOBBY_ROOM, callback_object)
@ -57,6 +57,7 @@ LobbyProtocol::~LobbyProtocol()
void LobbyProtocol::loadWorld()
{
Log::info("LobbyProtocol", "Ready !");
RewindManager::setEnable(true);
// Race startup sequence
// ---------------------
@ -124,9 +125,8 @@ void LobbyProtocol::loadWorld()
// Load the actual world.
m_game_setup->getRaceConfig()->loadWorld();
World::getWorld()->setNetworkWorld(true);
(new KartUpdateProtocol())->requestStart();
(new ControllerEventsProtocol())->requestStart();
(new GameEventsProtocol())->requestStart();
GameProtocol::createInstance()->requestStart();
std::make_shared<GameEventsProtocol>()->requestStart();
} // loadWorld
@ -135,9 +135,5 @@ void LobbyProtocol::loadWorld()
*/
void LobbyProtocol::terminateLatencyProtocol()
{
Protocol *p = ProtocolManager::getInstance()
->getProtocol(PROTOCOL_SYNCHRONIZATION);
LatencyProtocol *sp = dynamic_cast<LatencyProtocol*>(p);
if (sp)
sp->requestTerminate();
ProtocolManager::lock()->findAndTerminate(PROTOCOL_SYNCHRONIZATION);
} // stopLatencyProtocol

View File

@ -63,28 +63,33 @@ public:
};
protected:
static LobbyProtocol *m_lobby;
static std::weak_ptr<LobbyProtocol> m_lobby;
/** The game setup. */
GameSetup* m_game_setup;
public:
/** Creates either a client or server lobby protocol as a singleton. */
template<typename S> static S* create()
template<typename singleton> static std::shared_ptr<singleton> create()
{
assert(m_lobby == NULL);
m_lobby = new S();
return dynamic_cast<S*>(m_lobby);
assert(m_lobby.expired());
auto ret = std::make_shared<singleton>();
m_lobby = ret;
return std::dynamic_pointer_cast<singleton>(ret);
} // create
// ------------------------------------------------------------------------
/** Returns the singleton client or server lobby protocol. */
static LobbyProtocol *get()
template<class T> static std::shared_ptr<T> get()
{
assert(m_lobby);
return m_lobby;
if (std::shared_ptr<LobbyProtocol> lp = m_lobby.lock())
{
std::shared_ptr<T> new_type = std::dynamic_pointer_cast<T>(lp);
if (new_type)
return new_type;
}
return nullptr;
} // get
// ------------------------------------------------------------------------
@ -95,6 +100,7 @@ public:
virtual void update(float dt) = 0;
virtual void finishedLoadingWorld() = 0;
virtual void loadWorld();
virtual bool waitingForPlayers() const = 0;
void terminateLatencyProtocol();
virtual void requestKartSelection(uint8_t player_id,
const std::string &kart_name)

View File

@ -1,58 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2013-2015 SuperTuxKart-Team
//
// 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/protocols/ping_protocol.hpp"
#include "network/stk_host.hpp"
#include "utils/time.hpp"
/** Constructor. Stores the destination address and how often to ping.
* \param ping_dest: Destination of ping request.
* \param delay_between_pings: How often to ping.
*/
PingProtocol::PingProtocol(const TransportAddress& ping_dst,
double delay_between_pings)
: Protocol(PROTOCOL_SILENT)
{
m_ping_dst.copy(ping_dst);
m_delay_between_pings = delay_between_pings;
} // PingProtocol
// ----------------------------------------------------------------------------
PingProtocol::~PingProtocol()
{
} // ~PingProtocol
// ----------------------------------------------------------------------------
void PingProtocol::setup()
{
m_last_ping_time = 0;
} // setup
// ----------------------------------------------------------------------------
void PingProtocol::asynchronousUpdate()
{
if (StkTime::getRealTime() > m_last_ping_time+m_delay_between_pings)
{
m_last_ping_time = StkTime::getRealTime();
BareNetworkString data;
data.addUInt8(0);
STKHost::get()->sendRawPacket(data, m_ping_dst);
Log::info("PingProtocol", "Ping message sent");
}
} // asynchronousUpdate

View File

@ -1,35 +0,0 @@
#ifndef PING_PROTOCOL_HPP
#define PING_PROTOCOL_HPP
#include "network/protocol.hpp"
#include "network/transport_address.hpp"
#include "utils/cpp2011.hpp"
class PingProtocol : public Protocol
{
private:
/** The destination for the ping request. */
TransportAddress m_ping_dst;
/** How frequently to ping. */
double m_delay_between_pings;
/** Time of last ping. */
double m_last_ping_time;
public:
PingProtocol(const TransportAddress& ping_dst,
double delay_between_pings);
virtual ~PingProtocol();
virtual void asynchronousUpdate() OVERRIDE;
virtual void setup() OVERRIDE;
// ------------------------------------------------------------------------
virtual bool notifyEvent(Event* event) OVERRIDE { return true; }
// ------------------------------------------------------------------------
virtual bool notifyEventAsynchronous(Event* event) OVERRIDE { return true; }
// ------------------------------------------------------------------------
virtual void update(float dt) OVERRIDE {}
};
#endif // PING_PROTOCOL_HPP

View File

@ -18,13 +18,13 @@
#include "network/protocols/request_connection.hpp"
#include "config/player_manager.hpp"
#include "config/user_config.hpp"
#include "network/network.hpp"
#include "network/network_config.hpp"
#include "network/protocol_manager.hpp"
#include "network/servers_manager.hpp"
#include "network/stk_host.hpp"
#include "online/xml_request.hpp"
using namespace Online;
@ -75,20 +75,41 @@ void RequestConnection::asynchronousUpdate()
{
case NONE:
{
if(NetworkConfig::get()->isLAN())
if (NetworkConfig::get()->isLAN() ||
NetworkConfig::get()->isDirectConnect() ||
STKHost::get()->isClientServer())
{
const Server *server =
if (STKHost::get()->isClientServer())
{
// Allow up to 10 seconds for the separate process to
// fully start-up
double timeout = StkTime::getRealTime() + 10.;
while (StkTime::getRealTime() < timeout)
{
const std::string& sid = NetworkConfig::get()
->getServerIdFile();
assert(!sid.empty());
if (file_manager->fileExists(sid))
{
file_manager->removeFile(sid);
break;
}
StkTime::sleep(10);
}
NetworkConfig::get()->setServerIdFile("");
}
const Server *server =
ServersManager::get()->getServerByID(m_server_id);
BareNetworkString message(std::string("connection-request"));
STKHost::get()->sendRawPacket(message, server->getAddress());
NetworkConfig::get()->setDirectConnect(false);
m_state = DONE;
}
else
{
m_request = new ServerJoinRequest();
PlayerManager::setUserDetails(m_request, "request-connection",
Online::API::SERVER_PATH);
NetworkConfig::get()->setUserDetails(m_request,
"request-connection");
m_request->addParameter("server_id", m_server_id);
m_request->queue();
m_state = REQUEST_PENDING;

View File

@ -18,13 +18,12 @@
#include "network/protocols/server_lobby.hpp"
#include "config/player_manager.hpp"
#include "config/user_config.hpp"
#include "karts/kart_properties_manager.hpp"
#include "modes/world.hpp"
#include "network/event.hpp"
#include "network/network_config.hpp"
#include "network/network_player_profile.hpp"
#include "network/protocols/get_public_address.hpp"
#include "network/protocols/connect_to_peer.hpp"
#include "network/protocols/latency_protocol.hpp"
#include "network/protocol_manager.hpp"
@ -33,13 +32,16 @@
#include "network/stk_peer.hpp"
#include "online/online_profile.hpp"
#include "online/request_manager.hpp"
#include "race/race_manager.hpp"
#include "states_screens/networking_lobby.hpp"
#include "states_screens/race_result_gui.hpp"
#include "states_screens/waiting_for_others.hpp"
#include "tracks/track_manager.hpp"
#include "utils/log.hpp"
#include "utils/random_generator.hpp"
#include "utils/time.hpp"
#include <fstream>
/** This is the central game setup protocol running in the server. It is
* mostly a finite state machine. Note that all nodes in ellipses and light
@ -48,9 +50,9 @@
* change.
\dot
digraph interaction {
node [shape=box]; "Server Constructor"; "playerTrackVote"; "connectionRequested";
"signalRaceStartToClients"; "startedRaceOnClient"; "loadWorld";
node [shape=ellipse,style=filled,color=lightgrey];
node [shape=box]; "Server Constructor"; "playerTrackVote"; "connectionRequested";
"signalRaceStartToClients"; "startedRaceOnClient"; "loadWorld";
node [shape=ellipse,style=filled,color=lightgrey];
"Server Constructor" -> "INIT_WAN" [label="If WAN game"]
"Server Constructor" -> "ACCEPTING_CLIENTS" [label="If LAN game"]
@ -83,6 +85,17 @@
ServerLobby::ServerLobby() : LobbyProtocol(NULL)
{
setHandleDisconnections(true);
m_state = SET_PUBLIC_ADDRESS;
// We use maximum 16bit unsigned limit
auto all_k = kart_properties_manager->getAllAvailableKarts();
auto all_t = track_manager->getAllTrackIdentifiers();
if (all_k.size() >= 65536)
all_k.resize(65535);
if (all_t.size() >= 65536)
all_t.resize(65535);
m_available_kts.getData().first = { all_k.begin(), all_k.end() };
m_available_kts.getData().second = { all_t.begin(), all_t.end() };
} // ServerLobby
//-----------------------------------------------------------------------------
@ -90,22 +103,21 @@ ServerLobby::ServerLobby() : LobbyProtocol(NULL)
*/
ServerLobby::~ServerLobby()
{
if (m_server_registered && NetworkConfig::get()->isWAN())
{
unregisterServer();
}
} // ~ServerLobby
//-----------------------------------------------------------------------------
void ServerLobby::setup()
{
m_server_registered = false;
m_game_setup = STKHost::get()->setupNewGame();
m_game_setup->setNumLocalPlayers(0); // no local players on a server
m_next_player_id.setAtomic(0);
// In case of LAN we don't need our public address or register with the
// STK server, so we can directly go to the accepting clients state.
m_state = NetworkConfig::get()->isLAN() ? ACCEPTING_CLIENTS
: INIT_WAN;
m_selection_enabled = false;
m_current_protocol = NULL;
Log::info("ServerLobby", "Starting the protocol.");
// Initialise the data structures to detect if all clients and
@ -122,6 +134,29 @@ void ServerLobby::setup()
} // setup
//-----------------------------------------------------------------------------
bool ServerLobby::notifyEvent(Event* event)
{
assert(m_game_setup); // assert that the setup exists
if (event->getType() != EVENT_TYPE_MESSAGE)
return false;
NetworkString &data = event->data();
assert(data.size()); // message not empty
uint8_t message_type;
message_type = data.getUInt8();
Log::info("ServerLobby", "Synchronous message received with type %d.",
message_type);
switch (message_type)
{
case LE_REQUEST_BEGIN: startSelection(event); break;
default: Log::error("ServerLobby", "Unknown message type %d - ignored.",
message_type);
break;
} // switch message_type
return true;
} // notifyEvent
//-----------------------------------------------------------------------------
bool ServerLobby::notifyEventAsynchronous(Event* event)
@ -138,7 +173,6 @@ bool ServerLobby::notifyEventAsynchronous(Event* event)
switch(message_type)
{
case LE_CONNECTION_REQUESTED: connectionRequested(event); break;
case LE_REQUEST_BEGIN: startSelection(event); break;
case LE_KART_SELECTION: kartSelectionRequested(event); break;
case LE_CLIENT_LOADED_WORLD: finishedLoadingWorldClient(event); break;
case LE_STARTED_RACE: startedRaceOnClient(event); break;
@ -150,7 +184,6 @@ bool ServerLobby::notifyEventAsynchronous(Event* event)
case LE_VOTE_LAPS: playerLapsVote(event); break;
case LE_RACE_FINISHED_ACK: playerFinishedResult(event); break;
} // switch
} // if (event->getType() == EVENT_TYPE_MESSAGE)
else if (event->getType() == EVENT_TYPE_DISCONNECTED)
{
@ -160,41 +193,95 @@ bool ServerLobby::notifyEventAsynchronous(Event* event)
} // notifyEventAsynchronous
//-----------------------------------------------------------------------------
/** Simple finite state machine. First get the public ip address. Once this
/** Create the server id file to let the graphics server client connect. */
void ServerLobby::createServerIdFile()
{
const std::string& sid = NetworkConfig::get()->getServerIdFile();
if (!sid.empty())
{
std::fstream fs;
fs.open(sid, std::ios::out);
fs.close();
NetworkConfig::get()->setServerIdFile("");
}
} // createServerIdFile
//-----------------------------------------------------------------------------
/** Find out the public IP server or poll STK server asynchronously. */
void ServerLobby::asynchronousUpdate()
{
switch (m_state.load())
{
case SET_PUBLIC_ADDRESS:
{
// In case of LAN we don't need our public address or register with the
// STK server, so we can directly go to the accepting clients state.
if (NetworkConfig::get()->isLAN())
{
m_state = ACCEPTING_CLIENTS;
STKHost::get()->startListening();
createServerIdFile();
return;
}
STKHost::get()->setPublicAddress();
if (STKHost::get()->getPublicAddress().isUnset())
{
m_state = ERROR_LEAVE;
}
else
{
STKHost::get()->startListening();
m_state = REGISTER_SELF_ADDRESS;
}
break;
}
case REGISTER_SELF_ADDRESS:
{
// Register this server with the STK server. This will block
// this thread, but there is no need for the protocol manager
// to react to any requests before the server is registered.
registerServer();
if (m_server_registered)
{
m_state = ACCEPTING_CLIENTS;
createServerIdFile();
}
break;
}
case ACCEPTING_CLIENTS:
{
// Only poll the STK server if this is a WAN server.
if (NetworkConfig::get()->isWAN())
checkIncomingConnectionRequests();
break;
}
case ERROR_LEAVE:
{
requestTerminate();
m_state = EXITING;
STKHost::get()->setErrorMessage(_("Failed to setup server."));
STKHost::get()->requestShutdown();
break;
}
default:
break;
}
} // asynchronousUpdate
//-----------------------------------------------------------------------------
/** Simple finite state machine. Once this
* is known, register the server and its address with the stk server so that
* client can find it.
*/
void ServerLobby::update(float dt)
{
switch (m_state)
switch (m_state.load())
{
case INIT_WAN:
// Start the protocol to find the public ip address.
m_current_protocol = new GetPublicAddress(this);
m_current_protocol->requestStart();
m_state = GETTING_PUBLIC_ADDRESS;
// The callback from GetPublicAddress will wake this protocol up
requestPause();
break;
case GETTING_PUBLIC_ADDRESS:
{
Log::debug("ServerLobby", "Public address known.");
// Free GetPublicAddress protocol
delete m_current_protocol;
// Register this server with the STK server. This will block
// this thread, but there is no need for the protocol manager
// to react to any requests before the server is registered.
registerServer();
Log::info("ServerLobby", "Server registered.");
m_state = ACCEPTING_CLIENTS;
}
break;
case SET_PUBLIC_ADDRESS:
case REGISTER_SELF_ADDRESS:
case ACCEPTING_CLIENTS:
{
// Only poll the STK server if this is a WAN server.
if(NetworkConfig::get()->isWAN())
checkIncomingConnectionRequests();
// Waiting for asynchronousUpdate
break;
}
case SELECTING:
@ -209,13 +296,12 @@ void ServerLobby::update(float dt)
break;
case WAIT_FOR_WORLD_LOADED:
// Note that m_server_has_loaded_world is called by the main thread
// (same a the thread updating this protocol)
// (same as the thread updating this protocol)
m_client_ready_count.lock();
if (m_server_has_loaded_world &&
m_client_ready_count.getData() == m_game_setup->getPlayerCount())
{
signalRaceStartToClients();
m_server_delay = 0.02f;
m_client_ready_count.getData() = 0;
}
m_client_ready_count.unlock();
@ -223,13 +309,14 @@ void ServerLobby::update(float dt)
// they have started the race/
break;
case WAIT_FOR_RACE_STARTED:
// The function finishedLoadingWorldClient() will trigger the
// The function startedRaceOnClient() will trigger the
// next state.
break;
case DELAY_SERVER:
m_server_delay -= dt;
if (m_server_delay < 0)
if (m_server_delay < StkTime::getRealTime())
{
Log::verbose("ServerLobby", "End delay at %lf",
StkTime::getRealTime());
m_state = RACING;
World::getWorld()->setReadyToRace();
}
@ -252,34 +339,26 @@ void ServerLobby::update(float dt)
sendMessageToPeersChangingToken(exit_result_screen,
/*reliable*/true);
delete exit_result_screen;
m_state = ACCEPTING_CLIENTS;
m_state = NetworkConfig::get()->isLAN() ?
ACCEPTING_CLIENTS : REGISTER_SELF_ADDRESS;
RaceResultGUI::getInstance()->backToLobby();
// notify the network world that it is stopped
RaceEventManager::getInstance()->stop();
// stop race protocols
findAndTerminateProtocol(PROTOCOL_CONTROLLER_EVENTS);
findAndTerminateProtocol(PROTOCOL_KART_UPDATE);
findAndTerminateProtocol(PROTOCOL_GAME_EVENTS);
auto pm = ProtocolManager::lock();
assert(pm);
pm->findAndTerminate(PROTOCOL_CONTROLLER_EVENTS);
pm->findAndTerminate(PROTOCOL_KART_UPDATE);
pm->findAndTerminate(PROTOCOL_GAME_EVENTS);
setup();
}
break;
case DONE:
m_state = EXITING;
requestTerminate();
break;
case ERROR_LEAVE:
case EXITING:
break;
}
} // update
//-----------------------------------------------------------------------------
/** Callback when the GetPublicAddress terminates. It will unpause this
* protocol, which triggers the next state of the finite state machine.
*/
void ServerLobby::callback(Protocol *protocol)
{
requestUnpause();
} // callback
//-----------------------------------------------------------------------------
/** Register this server (i.e. its public address) with the STK server
* so that clients can find it. It blocks till a response from the
@ -290,17 +369,21 @@ void ServerLobby::callback(Protocol *protocol)
void ServerLobby::registerServer()
{
Online::XMLRequest *request = new Online::XMLRequest();
const TransportAddress& addr = NetworkConfig::get()->getMyAddress();
PlayerManager::setUserDetails(request, "create", Online::API::SERVER_PATH);
const TransportAddress& addr = STKHost::get()->getPublicAddress();
NetworkConfig::get()->setUserDetails(request, "create");
request->addParameter("address", addr.getIP() );
request->addParameter("port", addr.getPort() );
request->addParameter("private_port",
NetworkConfig::get()->getServerPort() );
STKHost::get()->getPrivatePort() );
request->addParameter("name", NetworkConfig::get()->getServerName() );
request->addParameter("max_players",
UserConfigParams::m_server_max_players );
Log::info("RegisterServer", "Showing addr %s", addr.toString().c_str());
request->addParameter("max_players",
NetworkConfig::get()->getMaxPlayers());
request->addParameter("difficulty", race_manager->getDifficulty());
request->addParameter("game_mode",
NetworkConfig::get()->getServerGameMode(race_manager->getMinorMode(),
race_manager->getMajorMode()));
Log::info("ServerLobby", "Public server addr %s", addr.toString().c_str());
request->executeNow();
const XMLNode * result = request->getXMLData();
@ -308,18 +391,56 @@ void ServerLobby::registerServer()
if (result->get("success", &rec_success) && rec_success == "yes")
{
Log::info("RegisterServer", "Server is now online.");
STKHost::get()->setRegistered(true);
Log::info("ServerLobby", "Server is now online.");
m_server_registered = true;
}
else
{
irr::core::stringc error(request->getInfo().c_str());
Log::error("RegisterServer", "%s", error.c_str());
STKHost::get()->setErrorMessage(_("Failed to register server: %s", error.c_str()));
Log::error("ServerLobby", "%s", error.c_str());
m_state = ERROR_LEAVE;
}
delete request;
} // registerServer
//-----------------------------------------------------------------------------
/** Unregister this server (i.e. its public address) with the STK server,
* currently when karts enter kart selection screen it will be done.
*/
void ServerLobby::unregisterServer()
{
const TransportAddress &addr = STKHost::get()->getPublicAddress();
Online::XMLRequest* request = new Online::XMLRequest();
NetworkConfig::get()->setUserDetails(request, "stop");
request->addParameter("address", addr.getIP());
request->addParameter("port", addr.getPort());
Log::info("ServerLobby", "address %s", addr.toString().c_str());
request->executeNow();
const XMLNode * result = request->getXMLData();
std::string rec_success;
if (result->get("success", &rec_success))
{
if (rec_success == "yes")
{
Log::info("ServerLobby", "Server is now unregister.");
}
else
{
Log::error("ServerLobby", "Fail to unregister server.");
}
}
else
{
Log::error("ServerLobby", "Fail to stop server.");
}
delete request;
} // unregisterServer
//-----------------------------------------------------------------------------
/** This function is called when all clients have loaded the world and
* are therefore ready to start the race. It signals to all clients
@ -327,6 +448,8 @@ void ServerLobby::registerServer()
*/
void ServerLobby::signalRaceStartToClients()
{
Log::verbose("Server", "Signaling race start to clients at %lf",
StkTime::getRealTime());
const std::vector<STKPeer*> &peers = STKHost::get()->getPeers();
NetworkString *ns = getNetworkString(1);
ns->addUInt8(LE_START_RACE);
@ -341,11 +464,18 @@ void ServerLobby::signalRaceStartToClients()
*/
void ServerLobby::startSelection(const Event *event)
{
if (NetworkConfig::get()->isWAN())
{
assert(m_server_registered);
unregisterServer();
m_server_registered = false;
}
if (m_state != ACCEPTING_CLIENTS)
{
Log::warn("ServerLobby",
"Received startSelection while being in state %d", m_state);
"Received startSelection while being in state %d",
m_state.load());
return;
}
if(event && !event->getPeer()->isAuthorised())
@ -357,8 +487,24 @@ void ServerLobby::startSelection(const Event *event)
}
const std::vector<STKPeer*> &peers = STKHost::get()->getPeers();
NetworkString *ns = getNetworkString(1);
// start selection
// Start selection - must be synchronous since the receiver pushes
// a new screen, which must be donefrom the main thread.
ns->setSynchronous(true);
ns->addUInt8(LE_START_SELECTION);
m_available_kts.lock();
const auto& all_k = m_available_kts.getData().first;
const auto& all_t = m_available_kts.getData().second;
ns->addUInt16((uint16_t)all_k.size()).addUInt16((uint16_t)all_t.size());
for (const std::string& kart : all_k)
{
ns->encodeString(kart);
}
for (const std::string& track : all_t)
{
ns->encodeString(track);
}
m_available_kts.unlock();
sendMessageToPeersChangingToken(ns, /*reliable*/true);
delete ns;
@ -367,8 +513,7 @@ void ServerLobby::startSelection(const Event *event)
m_state = SELECTING;
WaitingForOthersScreen::getInstance()->push();
Protocol *p = new LatencyProtocol();
p->requestStart();
std::make_shared<LatencyProtocol>()->requestStart();
Log::info("LobbyProtocol", "LatencyProtocol started.");
} // startSelection
@ -385,15 +530,21 @@ void ServerLobby::checkIncomingConnectionRequests()
if (StkTime::getRealTime() < last_poll_time + POLL_INTERVAL)
return;
// Keep the port open, it can be sent to anywhere as we will send to the
// correct peer later in ConnectToPeer.
BareNetworkString data;
data.addUInt8(0);
STKHost::get()->sendRawPacket(data, STKHost::get()->getStunAddress());
// Now poll the stk server
last_poll_time = StkTime::getRealTime();
Online::XMLRequest* request = new Online::XMLRequest();
PlayerManager::setUserDetails(request, "poll-connection-requests",
Online::API::SERVER_PATH);
NetworkConfig::get()->setUserDetails(request, "poll-connection-requests");
const TransportAddress &addr = NetworkConfig::get()->getMyAddress();
const TransportAddress &addr = STKHost::get()->getPublicAddress();
request->addParameter("address", addr.getIP() );
request->addParameter("port", addr.getPort());
request->addParameter("current_players", STKHost::get()->getPeerCount());
request->executeNow();
assert(request->isDone());
@ -415,9 +566,8 @@ void ServerLobby::checkIncomingConnectionRequests()
users_xml->getNode(i)->get("id", &id);
Log::debug("ServerLobby",
"User with id %d wants to connect.", id);
Protocol *p = new ConnectToPeer(id);
p->requestStart();
}
std::make_shared<ConnectToPeer>(id)->requestStart();
}
delete request;
} // checkIncomingConnectionRequests
@ -536,12 +686,71 @@ void ServerLobby::connectionRequested(Event* event)
// Connection accepted.
// ====================
std::string name_u8;
int len = data.decodeString(&name_u8);
data.decodeString(&name_u8);
core::stringw name = StringUtils::utf8ToWide(name_u8);
std::string password;
data.decodeString(&password);
bool is_authorised = (password==NetworkConfig::get()->getPassword());
std::set<std::string> client_karts, client_tracks;
const unsigned kart_num = data.getUInt16();
const unsigned track_num = data.getUInt16();
for (unsigned i = 0; i < kart_num; i++)
{
std::string kart;
data.decodeString(&kart);
client_karts.insert(kart);
}
for (unsigned i = 0; i < track_num; i++)
{
std::string track;
data.decodeString(&track);
client_tracks.insert(track);
}
// Remove karts/tracks from server that are not supported on the new client
// so that in the end the server has a list of all karts/tracks available
// on all clients
std::set<std::string> karts_erase, tracks_erase;
for (const std::string& server_kart : m_available_kts.getData().first)
{
if (client_karts.find(server_kart) == client_karts.end())
{
karts_erase.insert(server_kart);
}
}
for (const std::string& server_track : m_available_kts.getData().second)
{
if (client_tracks.find(server_track) == client_tracks.end())
{
tracks_erase.insert(server_track);
}
}
// Drop this player if he doesn't have at least 1 kart / track the same
// from server
if (karts_erase.size() == m_available_kts.getData().first.size() ||
tracks_erase.size() == m_available_kts.getData().second.size())
{
NetworkString *message = getNetworkString(2);
message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(3);
peer->sendPacket(message);
delete message;
Log::verbose("ServerLobby", "Player has incompatible karts / tracks");
m_available_kts.unlock();
return;
}
for (const std::string& kart_erase : karts_erase)
{
m_available_kts.getData().first.erase(kart_erase);
}
for (const std::string& track_erase : tracks_erase)
{
m_available_kts.getData().second.erase(track_erase);
}
m_available_kts.unlock();
// Get the unique global ID for this player.
m_next_player_id.lock();
m_next_player_id.getData()++;
@ -620,7 +829,7 @@ void ServerLobby::kartSelectionRequested(Event* event)
if(m_state!=SELECTING)
{
Log::warn("Server", "Received kart selection while in state %d.",
m_state);
m_state.load());
return;
}
@ -909,12 +1118,16 @@ void ServerLobby::finishedLoadingWorldClient(Event *event)
void ServerLobby::startedRaceOnClient(Event *event)
{
m_client_ready_count.lock();
Log::verbose("ServerLobby", "Host %d has started race.",
event->getPeer()->getHostId());
Log::verbose("ServerLobby", "Host %d has started race at %lf.",
event->getPeer()->getHostId(), StkTime::getRealTime());
m_client_ready_count.getData()++;
if (m_client_ready_count.getData() == m_game_setup->getPlayerCount())
{
m_state = DELAY_SERVER;
m_server_delay = StkTime::getRealTime() + 0.1f;
Log::verbose("ServerLobby", "Started delay at %lf set delay to %lf",
StkTime::getRealTime(),
m_server_delay);
terminateLatencyProtocol();
}
m_client_ready_count.unlock();
@ -927,7 +1140,7 @@ void ServerLobby::startedRaceOnClient(Event *event)
void ServerLobby::playerFinishedResult(Event *event)
{
m_player_ready_counter++;
if(m_player_ready_counter == STKHost::get()->getPeerCount())
if(m_player_ready_counter >= STKHost::get()->getPeerCount())
{
// We can't trigger the world/race exit here, since this is called
// from the protocol manager thread. So instead we force the timeout

View File

@ -5,27 +5,35 @@
#include "utils/cpp2011.hpp"
#include "utils/synchronised.hpp"
#include <atomic>
#include <set>
class ServerLobby : public LobbyProtocol
, public CallbackObject
{
private:
public:
/* The state for a small finite state machine. */
enum
enum ServerState : unsigned int
{
INIT_WAN, // Start state for WAN game
GETTING_PUBLIC_ADDRESS, // Waiting to receive its public ip address
SET_PUBLIC_ADDRESS, // Waiting to receive its public ip address
REGISTER_SELF_ADDRESS, // Register with STK online server
ACCEPTING_CLIENTS, // In lobby, accepting clients
SELECTING, // kart, track, ... selection started
LOAD_WORLD, // Server starts loading world
WAIT_FOR_WORLD_LOADED, // Wait for clients and server to load world
WAIT_FOR_RACE_STARTED, // Wait for all clients to have started the race
START_RACE, // Inform clients to start race
DELAY_SERVER, // Additional server delay
RACING, // racing
RESULT_DISPLAY, // Show result screen
DONE, // shutting down server
ERROR_LEAVE, // shutting down server
EXITING
} m_state;
};
private:
std::atomic<ServerState> m_state;
/** Available karts and tracks for all clients, this will be initialized
* with data in server first. */
Synchronised<std::pair<std::set<std::string>,
std::set<std::string> > > m_available_kts;
/** Next id to assign to a peer. */
Synchronised<int> m_next_player_id;
@ -43,12 +51,15 @@ private:
/** Keeps track of an artificial server delay (which makes sure that the
* data from all clients has arrived when the server computes a certain
* timestep. */
float m_server_delay;
* timestep.(. It stores the real time since epoch + delta (atm 0.1
* seconds), which is the real time at which the server should start. */
double m_server_delay;
Protocol *m_current_protocol;
bool m_selection_enabled;
/** It indicates if this server is registered with the stk server. */
std::atomic_bool m_server_registered;
/** Counts how many players are ready to go on. */
int m_player_ready_counter;
@ -71,22 +82,26 @@ private:
void registerServer();
void finishedLoadingWorldClient(Event *event);
void startedRaceOnClient(Event *event);
void unregisterServer();
void createServerIdFile();
public:
ServerLobby();
virtual ~ServerLobby();
virtual bool notifyEventAsynchronous(Event* event) OVERRIDE;
virtual bool notifyEvent(Event* event) OVERRIDE;
virtual void setup() OVERRIDE;
virtual void update(float dt) OVERRIDE;
virtual void asynchronousUpdate() OVERRIDE {};
virtual void asynchronousUpdate() OVERRIDE;
void signalRaceStartToClients();
void startSelection(const Event *event=NULL);
void checkIncomingConnectionRequests();
void checkRaceFinished();
void finishedLoadingWorld();
virtual void callback(Protocol *protocol) OVERRIDE;
ServerState getCurrentState() const { return m_state.load(); }
virtual bool waitingForPlayers() const OVERRIDE
{ return m_state.load() == ACCEPTING_CLIENTS; }
}; // class ServerLobby

View File

@ -1,89 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2013-2015 SuperTuxKart-Team
//
// 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/protocols/stop_server.hpp"
#include "config/player_manager.hpp"
#include "config/user_config.hpp"
#include "network/network_config.hpp"
#include "online/request_manager.hpp"
StopServer::StopServer() : Protocol(PROTOCOL_SILENT)
{
}
StopServer::~StopServer()
{
}
bool StopServer::notifyEventAsynchronous(Event* event)
{
return true;
}
void StopServer::setup()
{
m_state = NONE;
}
void StopServer::asynchronousUpdate()
{
if (m_state == NONE)
{
const TransportAddress& addr = NetworkConfig::get()->getMyAddress();
m_request = new Online::XMLRequest();
PlayerManager::setUserDetails(m_request, "stop", Online::API::SERVER_PATH);
m_request->addParameter("address", addr.getIP());
m_request->addParameter("port", addr.getPort());
Log::info("StopServer", "address %s", addr.toString().c_str());
Online::RequestManager::get()->addRequest(m_request);
m_state = REQUEST_PENDING;
}
else if (m_state == REQUEST_PENDING && m_request->isDone())
{
const XMLNode * result = m_request->getXMLData();
std::string rec_success;
if(result->get("success", &rec_success))
{
if(rec_success == "yes")
{
Log::info("StopServer", "Server is now offline.");
}
else
{
Log::error("StopServer", "Fail to stop server.");
}
}
else
{
Log::error("StopServer", "Fail to stop server.");
}
m_state = DONE;
}
else if (m_state == DONE)
{
m_state = EXITING;
delete m_request;
m_request = NULL;
requestTerminate();
}
} // asynchronousUpdate

View File

@ -1,36 +0,0 @@
#ifndef STOP_SERVER_HPP
#define STOP_SERVER_HPP
#include "network/protocol.hpp"
#include "utils/cpp2011.hpp"
namespace Online { class XMLRequest; }
/*! \brief Removes the server info from the database
*/
class StopServer : public Protocol
{
private:
Online::XMLRequest* m_request;
enum STATE
{
NONE,
REQUEST_PENDING,
DONE,
EXITING
};
STATE m_state;
public:
StopServer();
virtual ~StopServer();
virtual bool notifyEventAsynchronous(Event* event) OVERRIDE;
virtual void setup() OVERRIDE;
virtual void asynchronousUpdate() OVERRIDE;
// --------------------------------------------------------------------
virtual void update(float dt) OVERRIDE {}
};
#endif // STOP_SERVER_HPP

View File

@ -5,9 +5,9 @@
#include "modes/world.hpp"
#include "network/network_config.hpp"
#include "network/protocol_manager.hpp"
#include "network/protocols/controller_events_protocol.hpp"
#include "network/protocols/game_events_protocol.hpp"
#include "network/rewind_manager.hpp"
#include "utils/profiler.hpp"
RaceEventManager::RaceEventManager()
{
@ -25,15 +25,23 @@ RaceEventManager::~RaceEventManager()
*/
void RaceEventManager::update(float dt)
{
// This can happen in case of disconnects - protocol manager is
// shut down, but still events to process.
if(!ProtocolManager::getInstance())
return;
// Replay all recorded events up to the current time (only if the
// timer isn't stopped, otherwise a potential rewind will trigger
// an infinite loop since world time does not increase)
if (World::getWorld()->getPhase() != WorldStatus::IN_GAME_MENU_PHASE)
{
// This might adjust dt - if a new state is being played, the dt is
// determined from the last state till 'now'
PROFILER_PUSH_CPU_MARKER("RaceEvent:play event", 100, 100, 100);
RewindManager::get()->playEventsTill(World::getWorld()->getTime(),
&dt);
PROFILER_POP_CPU_MARKER();
}
World::getWorld()->updateWorld(dt);
// if the race is over
if (World::getWorld()->getPhase() >= WorldStatus::RESULT_DISPLAY_PHASE)
if (World::getWorld()->getPhase() >= WorldStatus::RESULT_DISPLAY_PHASE &&
World::getWorld()->getPhase() != WorldStatus::IN_GAME_MENU_PHASE)
{
// consider the world finished.
stop();
@ -58,14 +66,15 @@ bool RaceEventManager::isRaceOver()
{
if(!World::getWorld())
return false;
return (World::getWorld()->getPhase() > WorldStatus::RACE_PHASE);
return (World::getWorld()->getPhase() > WorldStatus::RACE_PHASE &&
World::getWorld()->getPhase() != WorldStatus::IN_GAME_MENU_PHASE);
} // isRaceOver
// ----------------------------------------------------------------------------
void RaceEventManager::kartFinishedRace(AbstractKart *kart, float time)
{
GameEventsProtocol* protocol = static_cast<GameEventsProtocol*>(
ProtocolManager::getInstance()->getProtocol(PROTOCOL_GAME_EVENTS));
auto protocol = std::static_pointer_cast<GameEventsProtocol>(
ProtocolManager::lock()->getProtocol(PROTOCOL_GAME_EVENTS));
protocol->kartFinishedRace(kart, time);
} // kartFinishedRace
@ -80,18 +89,8 @@ void RaceEventManager::collectedItem(Item *item, AbstractKart *kart)
// this is only called in the server
assert(NetworkConfig::get()->isServer());
GameEventsProtocol* protocol = static_cast<GameEventsProtocol*>(
ProtocolManager::getInstance()->getProtocol(PROTOCOL_GAME_EVENTS));
auto protocol = std::static_pointer_cast<GameEventsProtocol>(
ProtocolManager::lock()->getProtocol(PROTOCOL_GAME_EVENTS));
protocol->collectedItem(item,kart);
} // collectedItem
// ----------------------------------------------------------------------------
void RaceEventManager::controllerAction(Controller* controller,
PlayerAction action, int value)
{
ControllerEventsProtocol* protocol = static_cast<ControllerEventsProtocol*>(
ProtocolManager::getInstance()->getProtocol(PROTOCOL_CONTROLLER_EVENTS));
if (protocol)
protocol->controllerAction(controller, action, value);
} // controllerAction

View File

@ -53,8 +53,6 @@ public:
bool isRaceOver();
void collectedItem(Item *item, AbstractKart *kart);
void controllerAction(Controller* controller, PlayerAction action,
int value);
void kartFinishedRace(AbstractKart *kart, float time);
// ------------------------------------------------------------------------
/** Returns if this instance is in running state or not. */

View File

@ -18,6 +18,7 @@
#include "network/rewind_info.hpp"
#include "network/network_config.hpp"
#include "physics/physics.hpp"
/** Constructor for a state: it only takes the size, and allocates a buffer
@ -30,25 +31,33 @@ RewindInfo::RewindInfo(float time, bool is_confirmed)
m_is_confirmed = is_confirmed;
} // RewindInfo
// ============================================================================
RewindInfoTime::RewindInfoTime(float time)
: RewindInfo(time, /*is_confirmed*/true)
// ----------------------------------------------------------------------------
/** Adjusts the time of this RewindInfo. This is only called on the server
* in case that an event is received in the past - in this case the server
* needs to avoid a Rewind by moving this event forward to the current time.
*/
void RewindInfo::setTime(float time)
{
} // RewindInfoTime
assert(NetworkConfig::get()->isServer());
assert(m_time < time);
m_time = time;
} // setTime
// ============================================================================
RewindInfoState::RewindInfoState(float time, Rewinder *rewinder,
BareNetworkString *buffer, bool is_confirmed)
: RewindInfoRewinder(time, rewinder, buffer, is_confirmed)
{
m_local_physics_time = Physics::getInstance()->getPhysicsWorld()
->getLocalTime();
// rewinder = NULL is used in unit testing, in which case no world exists
if(rewinder!=NULL)
m_local_physics_time = Physics::getInstance()->getPhysicsWorld()
->getLocalTime();
} // RewindInfoState
// ============================================================================
RewindInfoEvent::RewindInfoEvent(float time, EventRewinder *event_rewinder,
BareNetworkString *buffer, bool is_confirmed)
: RewindInfo(time, is_confirmed)
: RewindInfo(time, is_confirmed)
{
m_event_rewinder = event_rewinder;
m_buffer = buffer;

View File

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

422
src/network/rewind_manager.cpp Normal file → Executable file
View File

@ -20,12 +20,18 @@
#include "graphics/irr_driver.hpp"
#include "modes/world.hpp"
#include "network/network_config.hpp"
#include "network/network_string.hpp"
#include "network/protocols/game_protocol.hpp"
#include "network/rewinder.hpp"
#include "network/rewind_info.hpp"
#include "network/time_step_info.hpp"
#include "physics/physics.hpp"
#include "race/history.hpp"
#include "utils/log.hpp"
#include "utils/profiler.hpp"
#include <algorithm>
RewindManager* RewindManager::m_rewind_manager = NULL;
bool RewindManager::m_enable_rewind_manager = false;
@ -61,13 +67,6 @@ RewindManager::RewindManager()
*/
RewindManager::~RewindManager()
{
// Destroying the
for(unsigned int i=0; i<m_rewind_info.size(); i++)
{
delete m_rewind_info[i];
m_rewind_info[i] = NULL;
}
m_rewind_info.clear();
} // ~RewindManager
// ----------------------------------------------------------------------------
@ -75,13 +74,10 @@ RewindManager::~RewindManager()
*/
void RewindManager::reset()
{
#ifdef REWIND_SEARCH_STATS
m_count_of_comparisons = 0;
m_count_of_searches = 0;
#endif
m_is_rewinding = false;
m_not_rewound_time = 0;
m_overall_state_size = 0;
m_state_frequency = 0.1f; // save 10 states a second
m_state_frequency = 1.0f / stk_config->m_network_state_frequeny;
m_last_saved_state = -9999.9f; // forces initial state save
if(!m_enable_rewind_manager) return;
@ -96,122 +92,37 @@ void RewindManager::reset()
}
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();
m_rewind_queue.reset();
} // 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 state
{
// 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;
}
} // 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.
/** 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.
*/
unsigned int RewindManager::findFirstIndex(float target_time) const
void RewindManager::addNextTimeStep(float time, float dt)
{
// 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
int index = (int)m_rewind_info.size()-1;
int index_last_state = -1;
while(index>=0)
// 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() ) &&
World::getWorld()->getPhase() != WorldStatus::IN_GAME_MENU_PHASE )
{
#ifdef REWIND_SEARCH_STATS
m_count_of_comparisons++;
#endif
if(m_rewind_info[index]->isState())
{
if(m_rewind_info[index]->getTime()<target_time)
{
return index;
}
index_last_state = index;
}
index--;
m_rewind_queue.addNewTimeStep(time, dt);
}
} // addNextTimeStep
if(index_last_state<0)
{
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, m_rewind_info[index_last_state]->getTime());
return index_last_state; // avoid compiler warning
} // findFirstIndex
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
/** Adds an event to the rewind data. The data to be stored must be allocated
* and not freed by the caller!
* \param time Time at which the event was recorded.
* \param time Time at which the event was recorded. If time is not specified
* (or set to -1), the current world time is used.
* \param buffer Pointer to the event data.
*/
void RewindManager::addEvent(EventRewinder *event_rewinder,
BareNetworkString *buffer)
BareNetworkString *buffer, bool confirmed,
float time )
{
if(m_is_rewinding)
{
@ -219,93 +130,170 @@ void RewindManager::addEvent(EventRewinder *event_rewinder,
Log::error("RewindManager", "Adding event when rewinding");
return;
}
RewindInfo *ri = new RewindInfoEvent(getCurrentTime(), event_rewinder,
buffer, /*is confirmed*/true);
insertRewindInfo(ri);
if (time < 0)
time = World::getWorld()->getTime();
m_rewind_queue.addLocalEvent(event_rewinder, buffer, confirmed, time);
} // addEvent
// ----------------------------------------------------------------------------
/** 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 RewindManager::addNetworkEvent(EventRewinder *event_rewinder,
BareNetworkString *buffer, float time)
{
m_rewind_queue.addNetworkEvent(event_rewinder, buffer, time);
} // addNetworkEvent
// ----------------------------------------------------------------------------
/** Adds a state to the list of network rewind data. This function is
* threadsafe so can be called by the network thread. The data is synched
* to m_rewind_info by the main thread. The data to be stored must be
* 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::addNetworkState(int rewinder_index, BareNetworkString *buffer,
float time)
{
assert(NetworkConfig::get()->isClient());
// On a client dt from a state is never used, it maintains
// its own dt information (using TimeEvents).
m_rewind_queue.addNetworkState(m_all_rewinder[rewinder_index], buffer,
time, -99);
} // addNetworkState
// ----------------------------------------------------------------------------
/** Determines if a new state snapshot should be taken, and if so calls all
* rewinder to do so.
* \param dt Time step size.
*/
void RewindManager::saveStates()
void RewindManager::update(float dt)
{
if(!m_enable_rewind_manager ||
m_all_rewinder.size()==0 ||
m_is_rewinding ) return;
float time = World::getWorld()->getTime();
if(time - m_last_saved_state < m_state_frequency)
m_not_rewound_time = time;
// Clients don't save state, so they just exit.
if ( NetworkConfig::get()->isClient() ||
time - m_last_saved_state < m_state_frequency )
{
// No full state necessary, add a dummy entry for the time
// which increases replay precision (same time step size)
RewindInfo *ri = new RewindInfoTime(getCurrentTime());
insertRewindInfo(ri);
return;
}
PROFILER_PUSH_CPU_MARKER("RewindManager - save state", 0x20, 0x7F, 0x20);
// For now always create a snapshot.
for(unsigned int i=0; i<m_all_rewinder.size(); i++)
// Save state
GameProtocol::lock()->startNewState();
AllRewinder::const_iterator rewinder;
for(rewinder=m_all_rewinder.begin(); rewinder!=m_all_rewinder.end(); ++rewinder)
{
BareNetworkString *buffer = m_all_rewinder[i]->saveState();
BareNetworkString *buffer = (*rewinder)->saveState();
if(buffer && buffer->size()>=0)
{
m_overall_state_size += buffer->size();
RewindInfo *ri = new RewindInfoState(getCurrentTime(),
m_all_rewinder[i], buffer,
/*is_confirmed*/true);
assert(ri);
insertRewindInfo(ri);
// Add to the previously created container
m_rewind_queue.addLocalState(*rewinder, buffer, /*confirmed*/true,
World::getWorld()->getTime());
GameProtocol::lock()->addState(buffer);
} // size >= 0
else
delete buffer; // NULL or 0 byte buffer
}
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) );
PROFILER_POP_CPU_MARKER();
PROFILER_PUSH_CPU_MARKER("RewindManager - send state", 0x20, 0x7F, 0x40);
GameProtocol::lock()->sendState();
PROFILER_POP_CPU_MARKER();
m_last_saved_state = time;
} // saveStates
} // update
// ----------------------------------------------------------------------------
/** Rewinds to the specified time.
* \param t Time to rewind to.
/** Replays all events from the last event played till the specified time.
* \param time Up to (and inclusive) which time events will be replayed.
* \param dt Time step size. This might get adjusted if a new state has
* been received.
*/
void RewindManager::playEventsTill(float time, 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);
if (needs_rewind)
{
Log::setPrefix("Rewind");
PROFILER_PUSH_CPU_MARKER("Rewind", 128, 128, 128);
rewindTo(rewind_time);
PROFILER_POP_CPU_MARKER();
Log::setPrefix("");
TimeStepInfo *tsi = m_rewind_queue.getCurrent();
World::getWorld()->setTime(tsi->getTime());
Physics::getInstance()->getPhysicsWorld()->resetLocalTime();
}
if (m_rewind_queue.isEmpty()) return;
// This is necessary to avoid that rewinding an event will store the
// event again as a seemingly new event.
assert(!m_is_rewinding);
m_is_rewinding = true;
// Now play all events between time and time + dt, i.e. all events
// stored at the last TimeStep info in the rewind queue.
//assert(m_rewind_queue.getLast() == m_rewind_queue.getCurrent());
TimeStepInfo *tsi = m_rewind_queue.getLast();
// ++m_rewind_queue; // Point to end of queue now
tsi->replayAllEvents();
if (tsi->hasConfirmedState() && NetworkConfig::get()->isClient())
{
Log::warn("RewindManager",
"Client has received state in the future: at %f state %f",
World::getWorld()->getTime(), tsi->getTime());
}
m_is_rewinding = false;
} // playEventsTill
// ----------------------------------------------------------------------------
/** Rewinds to the specified time, then goes forward till the current
* World::getTime() is reached again: it will replay everything before
* World::getTime(), but not the events at World::getTime() (or later)/
* \param rewind_time Time to rewind to.
*/
void RewindManager::rewindTo(float rewind_time)
{
assert(!m_is_rewinding);
m_is_rewinding = true;
Log::info("rewind", "Rewinding to %f", rewind_time);
bool is_history = history->replayHistory();
history->doReplayHistory(History::HISTORY_NONE);
// First find the state to which we need to rewind
// ------------------------------------------------
unsigned int index = findFirstIndex(rewind_time);
if(!m_rewind_info[index]->isState())
// First save all current transforms so that the error
// can be computed between the transforms before and after
// the rewind.
AllRewinder::iterator rewinder;
for (rewinder = m_all_rewinder.begin();
rewinder != m_all_rewinder.end(); ++rewinder)
{
Log::error("RewindManager", "No state for rewind to %f, state %d.",
rewind_time, index);
return;
(*rewinder)->saveTransform();
}
// Then undo the rewind infos going backwards in time
// --------------------------------------------------
for(int i=(int)m_rewind_info.size()-1; i>=(int)index; i--)
{
m_rewind_info[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(m_rewind_info[i]->isState() &&
m_rewind_info[i]->getTime() > m_rewind_info[index]->getTime() )
m_rewind_info[i]->setConfirmed(false);
} // for i>state
m_is_rewinding = true;
m_rewind_queue.undoUntil(rewind_time);
// Rewind the required state(s)
// ----------------------------
@ -313,93 +301,63 @@ void RewindManager::rewindTo(float rewind_time)
float current_time = world->getTime();
// Get the (first) full state to which we have to rewind
RewindInfoState *state =
dynamic_cast<RewindInfoState*>(m_rewind_info[index]);
TimeStepInfo *current = m_rewind_queue.getCurrent();
// Store the time to which we have to replay to
float exact_rewind_time = state->getTime();
// Store the time to which we have to replay to,
// which can be earlier than rewind_time
float exact_rewind_time = current->getTime();
// Now start the rewind with the full state:
world->setTime(exact_rewind_time);
float local_physics_time = state->getLocalPhysicsTime();
float local_physics_time = current->getLocalPhysicsTime();
Physics::getInstance()->getPhysicsWorld()->setLocalTime(local_physics_time);
// Restore all states from the current time - the full state of a race
// will be potentially stored in several state objects. State can be NULL
// if the next event is not a state
while(state && state->getTime()==exact_rewind_time)
float dt = -1.0f;
// Need to exit loop if in-game menu is open, since world clock
// will not be increased while the game is paused
if (World::getWorld()->getPhase() == WorldStatus::IN_GAME_MENU_PHASE)
{
state->rewind();
index++;
if(index>=m_rewind_info.size()) break;
state = dynamic_cast<RewindInfoState*>(m_rewind_info[index]);
m_is_rewinding = false;
history->doReplayHistory(History::HISTORY_PHYSICS);
return;
}
// Now go forward through the list of rewind infos:
// ------------------------------------------------
while( world->getTime() < current_time &&
index < (int)m_rewind_info.size() )
// Restore state from the current time
current->replayAllStates();
// Now go forward through the list of rewind infos. A new timestep
// info for the current time has already been added previously, so
// we rewind till we have reached the last timestep entry (which is
// the current time step).
while (current !=m_rewind_queue.getLast())
{
// Now handle all states and events at the current time before
// updating the world:
while(index < (int)m_rewind_info.size() &&
m_rewind_info[index]->getTime()<=world->getTime()+0.001f)
{
if(m_rewind_info[index]->isState())
{
// TOOD: replace the old state with a new state.
// For now just set it to confirmed
m_rewind_info[index]->setConfirmed(true);
}
else if(m_rewind_info[index]->isEvent())
{
m_rewind_info[index]->rewind();
}
index++;
}
float dt = determineTimeStepSize(index, current_time);
// Now handle all events(!) at the current time (i.e. between
// World::getTime() and World::getTime()+dt) before updating
// the world:
current->replayAllEvents();
dt = current->getDT();
world->updateWorld(dt);
#define SHOW_ROLLBACK
#undef SHOW_ROLLBACK
#ifdef SHOW_ROLLBACK
irr_driver->update(dt);
#endif
world->updateTime(dt);
}
m_is_rewinding = false;
++m_rewind_queue;
current = m_rewind_queue.getCurrent();
world->setTime(current->getTime());
} // while (world->getTime() < current_time)
// Now compute the errors which need to be visually smoothed
for (rewinder = m_all_rewinder.begin();
rewinder != m_all_rewinder.end(); ++rewinder)
{
(*rewinder)->computeError();
}
if(is_history)
history->doReplayHistory(History::HISTORY_PHYSICS);
m_is_rewinding = false;
} // 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(int 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 < (int)m_rewind_info.size())
return m_rewind_info[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 - m_rewind_info[next_state-1]->getTime();
float dt = 1.0f/60.0f;
float t = World::getWorld()->getTime();
if(m_rewind_info[next_state]->getTime() < t + dt)
{
// Since we have RewindInfo at that time, it is certain that
/// this time is before (or at) end_time, not after.
return m_rewind_info[next_state]->getTime()-t;
}
return t+dt < end_time ? dt : end_time - t;
} // determineTimeStepSize
// ----------------------------------------------------------------------------

View File

@ -20,9 +20,12 @@
#define HEADER_REWIND_MANAGER_HPP
#include "network/rewinder.hpp"
#include "network/rewind_queue.hpp"
#include "utils/ptr_vector.hpp"
#include "utils/synchronised.hpp"
#include <assert.h>
#include <list>
#include <vector>
class RewindInfo;
@ -88,10 +91,8 @@ private:
/** A list of all objects that can be rewound. */
AllRewinder m_all_rewinder;
/** Pointer to all saved states. */
typedef std::vector<RewindInfo*> AllRewindInfo;
AllRewindInfo m_rewind_info;
/** The queue that stores all rewind infos. */
RewindQueue m_rewind_queue;
/** Overall amount of memory allocated by states. */
unsigned int m_overall_state_size;
@ -105,62 +106,26 @@ private:
/** Time at which the last state was saved. */
float m_last_saved_state;
/** The current time to be used in all states/events. This is used to
* give all states and events during one frame the same time, even
* if e.g. states are saved before world time is increased, other
* events later. */
float m_current_time;
/** The current time step size. */
float m_time_step;
#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
/** This stores the original World time during a rewind. It is used to
* detect if a client's local time need adjustment to reduce rewinds. */
float m_not_rewound_time;
RewindManager();
~RewindManager();
unsigned int findFirstIndex(float time) const;
void insertRewindInfo(RewindInfo *ri);
float determineTimeStepSize(int state, float max_time);
~RewindManager();
public:
// First static functions to manage rewinding.
// ===========================================
static RewindManager *create();
static void destroy();
// ------------------------------------------------------------------------
/** Sets the time that is to be used for all further states or events,
* and the time step size. This is necessary so that states/events before
* and after World::m_time is increased have the same time stamp.
* \param t Time.
* \param dt Time step size.
*/
void setCurrentTime(float t, float dt)
{
m_current_time = t;
m_time_step = dt;
} // setCurrentTime
// ------------------------------------------------------------------------
/** Returns the current time. */
float getCurrentTime() const { return m_current_time; }
// ------------------------------------------------------------------------
float getCurrentTimeStep() const { return m_time_step; }
// ------------------------------------------------------------------------
/** En- or disables rewinding. */
static void setEnable(bool m) { m_enable_rewind_manager = m;}
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
/** Returns the singleton. This function will not automatically create
* the singleton. */
static RewindManager *get()
{
@ -168,12 +133,19 @@ public:
return m_rewind_manager;
} // get
// ------------------------------------------------------------------------
// Non-static function declarations:
void reset();
void saveStates();
void update(float dt);
void rewindTo(float target_time);
void addEvent(EventRewinder *event_rewinder, BareNetworkString *buffer);
void playEventsTill(float time, float *dt);
void addEvent(EventRewinder *event_rewinder, BareNetworkString *buffer,
bool confirmed, float time = -1.0f);
void addNetworkEvent(EventRewinder *event_rewinder,
BareNetworkString *buffer, float time);
void addNetworkState(int rewinder_index, BareNetworkString *buffer,
float time);
void addNextTimeStep(float time, float dt);
// ------------------------------------------------------------------------
/** Adds a Rewinder to the list of all rewinders.
* \return true If rewinding is enabled, false otherwise.
@ -187,6 +159,8 @@ public:
// ------------------------------------------------------------------------
/** Returns true if currently a rewind is happening. */
bool isRewinding() const { return m_is_rewinding; }
// ------------------------------------------------------------------------
float getNotRewoundWorldTime() const { return m_not_rewound_time; }
}; // RewindManager

513
src/network/rewind_queue.cpp Executable file
View File

@ -0,0 +1,513 @@
//
// 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 "config/stk_config.hpp"
#include "modes/world.hpp"
#include "network/network_config.hpp"
#include "network/rewind_info.hpp"
#include "network/rewind_manager.hpp"
#include "network/time_step_info.hpp"
#include <algorithm>
/** The RewindQueue stores one TimeStepInfo for each time step done.
* The TimeStepInfo stores all states and events to be used at the
* given timestep.
* All network events (i.e. new states or client events) are stored in a
* separate list m_network_events. At the very start of a new time step
* a new TimeStepInfo object is added. Then all network events that are
* supposed to happen between t and t+dt are added to this newly added
* TimeStep (see mergeNetworkData), and are then being executed.
* In case of a rewind the RewindQueue finds the last TimeStepInfo with
* a confirmed server state (undoing the events, see undoUntil). Then
* the state is restored from the TimeStepInfo object (see replayAllStates)
* then the rewind manager re-executes the time steps (using the events
* stored at each timestep).
*/
RewindQueue::RewindQueue()
{
m_current = m_time_step_info.begin();
reset();
} // RewindQueue
// ----------------------------------------------------------------------------
/** Frees all saved state information. Note that the Rewinder data must be
* freed elsewhere.
*/
RewindQueue::~RewindQueue()
{
// Destroying the
AllTimeStepInfo::const_iterator i;
for(i=m_time_step_info.begin(); i!=m_time_step_info.end(); ++i)
{
delete *i;
}
m_time_step_info.clear();
} // ~RewindQueue
// ----------------------------------------------------------------------------
/** Frees all saved state information and all destroyable rewinder.
*/
void RewindQueue::reset()
{
for(AllTimeStepInfo::const_iterator i =m_time_step_info.begin();
i!=m_time_step_info.end(); i++)
{
delete *i;
}
m_time_step_info.clear();
m_current = m_time_step_info.begin();
m_network_events.lock();
AllNetworkRewindInfo &info = m_network_events.getData();
for (AllNetworkRewindInfo::const_iterator i = info.begin();
i != info.end(); ++i)
{
delete *i;
}
m_network_events.getData().clear();
m_network_events.unlock();
} // reset
// ----------------------------------------------------------------------------
/** Adds a new TimeStepInfo for the specified time. The TimeStepInfo acts
* as an container to store all states and events that happen at this time
* (or at least close to this time, since e.g. several events from clients
* happening at slightly different times will be all handled in the same
* timestep.
* \param time New time to add.
* \param dt Time step size that is going to be used for this time step.
*/
void RewindQueue::addNewTimeStep(float time, float dt)
{
TimeStepInfo *tsi = new TimeStepInfo(time, dt);
assert(m_time_step_info.empty() ||
time > m_time_step_info.back()->getTime() );
m_time_step_info.push_back(tsi);
// If current was not initialised
if (m_current == m_time_step_info.end())
{
m_current--;
}
} // addNewTimeStep
// ----------------------------------------------------------------------------
/** Finds the TimeStepInfo object to which an event at time t should be added.
* The TimeStepInfo object might not have the exacct same time, it can be
* the closest existing (or in future this function might even add a totally
* new TimeStepInfo object).
* \param Time at which the event that needs to be added hapened.
*/
RewindQueue::AllTimeStepInfo::iterator
RewindQueue::findPreviousTimeStepInfo(float t)
{
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
* 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::_TimeStepInfoCompare::operator()(const TimeStepInfo * const ri1,
const TimeStepInfo * const ri2) const
{
return ri1->getTime() < ri2->getTime();
} // 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)
{
// FIXME: this should always be the last element in the list(??)
AllTimeStepInfo::iterator bucket = findPreviousTimeStepInfo(ri->getTime());
// FIXME: In case of a history replay an element could be inserted in the
// very first frame (on very quick recorded start, and if the first frame
// takes a long time - e.g. in networking startup), i.e. before a TimeStep
// info was added. Since this is mostly for debugging, just ignore this
// this for now.
if(bucket!=m_time_step_info.end())
(*bucket)->insert(ri);
} // 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::addLocalEvent(EventRewinder *event_rewinder,
BareNetworkString *buffer, bool confirmed,
float time )
{
RewindInfo *ri = new RewindInfoEvent(time, event_rewinder,
buffer, confirmed);
insertRewindInfo(ri);
} // addLocalEvent
// ----------------------------------------------------------------------------
/** Adds a state from the local simulation to the last created TimeStepInfo
* container with the current world time. It is not thread-safe, so needs
* to be called from the main thread.
* \param rewinder The rewinder object for this state.
* \param buffer The state information.
* \param confirmed If this state is confirmed to be correct (e.g. is
* being received from the servrer), or just a local state for
* faster rewinds.
* \param time Time at which the state was captured.
*/
void RewindQueue::addLocalState(Rewinder *rewinder, BareNetworkString *buffer,
bool confirmed, float time)
{
RewindInfo *ri = new RewindInfoState(time, rewinder, buffer, confirmed);
assert(ri);
insertRewindInfo(ri);
} // addLocalState
// ----------------------------------------------------------------------------
/** Adds an event to the list of network rewind data. This function is
* threadsafe so can be called by the network thread. The data is synched
* to m_time_step_info by the main thread. The data to be stored must be
* allocated and not freed by the caller!
* \param time Time at which the event was recorded.
* \param buffer Pointer to the event data.
*/
void RewindQueue::addNetworkEvent(EventRewinder *event_rewinder,
BareNetworkString *buffer, float time)
{
RewindInfo *ri = new RewindInfoEvent(time, event_rewinder,
buffer, /*confirmed*/true);
m_network_events.lock();
m_network_events.getData().push_back(ri);
m_network_events.unlock();
} // addNetworkEvent
// ----------------------------------------------------------------------------
/** Adds a state to the list of network rewind data. This function is
* threadsafe so can be called by the network thread. The data is synched
* to m_time_step_info by the main thread. The data to be stored must be
* allocated and not freed by the caller!
* \param time Time at which the event was recorded.
* \param buffer Pointer to the event data.
*/
void RewindQueue::addNetworkState(Rewinder *rewinder, BareNetworkString *buffer,
float time, float dt)
{
RewindInfo *ri = new RewindInfoState(time, rewinder,
buffer, /*confirmed*/true);
m_network_events.lock();
m_network_events.getData().push_back(ri);
m_network_events.unlock();
} // addNetworkState
// ----------------------------------------------------------------------------
/** Merges thread-safe all data received from the network with the current
* local rewind information.
* \param world_time[in] Current world time up to which network events will be
* merged in.
* \param dt[in] Time step size. The current frame will cover events between
* world_time and world_time+dt.
* \param needs_rewind[out] True if network rewind information was received
* which was in the past (of this simulation), so a rewind must be
* performed.
* \param rewind_time[out] 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(float world_time, float dt,
bool *needs_rewind, float *rewind_time)
{
*needs_rewind = false;
m_network_events.lock();
if(m_network_events.getData().empty())
{
m_network_events.unlock();
return;
}
// Merge all newly received network events into the main event list.
// Only a client ever rewinds. So the rewind time should be the latest
// received state before current world time (if any)
*rewind_time = -99999.9f;
bool adjust_next = false;
// 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() )
{
// Ignore any events that will happen in the future. An event needs
// to be handled at the closest time to its original time. The current
// time step id world_time, the next will be world_time+dt. So if the
// event is later than world_time+0.5*dt, it will be closer to a
// future time stamp and is ignored now.
if ((*i)->getTime() > world_time+0.5f*dt)
{
i++;
continue;
}
// A server never rewinds (otherwise we would have to handle
// duplicated states, which in the best case would then have
// a negative effect for every player, when in fact only one
// player might have a network hickup).
if (NetworkConfig::get()->isServer() && (*i)->getTime() < world_time)
{
Log::warn("RewindQueue", "At %f received message from %f",
world_time, (*i)->getTime());
// Server received an event in the past. Adjust this event
// to be executed now - at least we get a bit closer to the
// client state.
(*i)->setTime(world_time);
}
// 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 type %c to timstepinfo %f prev %f next %f",
(*i)->getTime(),
(*i)->isEvent() ? 'E' : ((*i)->isState() ? 'S' : 'T'),
tsi->getTime(),
(*prev)->getTime(),
next != m_time_step_info.end() ? (*next)->getTime() : 9999 );
// Check if a rewind is necessary: either an message arrived in the past
// or if the time is between world_time and world_time+dt (otherwise
// the message would have been ignored further up), 'rewind' to this new
// state anyway
if (NetworkConfig::get()->isClient())
{
// We need rewind if we either receive an event in the past
// (FIXME: maybe we can just ignore this since we will also get
// a state update??), or receive a state from the current time
// (i.e. between world_time and world_time+dt). In the latter
// case we can just 'rewind' to this stage instead of doing a
// full simulation - though this client should potentially
// speed up a bit: if it receives a state from the server
// at the time the client is currently simulating (instead of
// triggering a rollback) it is not ahead enough of the server
// which will trigger a time adjustment from the server anyway.
if (tsi->getTime() < world_time ||
(*i)->isState() && tsi == m_time_step_info.back())
{
*needs_rewind = true;
if (tsi->getTime() > *rewind_time) *rewind_time = tsi->getTime();
}
} // if client
i = m_network_events.getData().erase(i);
} // for i in m_network_events
m_network_events.unlock();
} // mergeNetworkData
// ----------------------------------------------------------------------------
bool RewindQueue::isEmpty() const
{
return m_time_step_info.empty();
} // isEmpty
// ----------------------------------------------------------------------------
/** Returns true if there is at least one more RewindInfo available.
*/
bool RewindQueue::hasMoreRewindInfo() const
{
return m_current != m_time_step_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_time_step_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 confirmed state is reached that was recorded before the
* undo_time and sets the internal 'current' pointer to this state. It is
* assumed that this function is called after a new TimeStepInfo instance
* was added (i.e. after RewindManager::update() was called), so the state
* m_current is pointing to is ignored.
* \param undo_time To what at least events need to be undone.
*/
void RewindQueue::undoUntil(float undo_time)
{
while (m_current != m_time_step_info.begin())
{
--m_current;
// Undo all events and states from the current time
(*m_current)->undoAll();
if ((*m_current)->getTime() <= undo_time &&
(*m_current)->hasConfirmedState())
{
return;
}
} // while m_current!=m_time_step_info.begin()
Log::error("RewindManager", "No state for rewind to %f",
undo_time);
} // undoUntil
// ----------------------------------------------------------------------------
/** 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) {}
virtual void saveTransform() {}
virtual void computeError() {}
DummyRewinder() : Rewinder(true) {}
};
DummyRewinder *dummy_rewinder = new DummyRewinder();
RewindQueue q0;
assert(q0.isEmpty());
assert(!q0.hasMoreRewindInfo());
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);
bool needs_rewind;
float rewind_time;
float world_time = 0.0f;
float dt = 0.01f;
assert((*q0.m_time_step_info.begin())->getNumberOfEvents() == 1);
q0.mergeNetworkData(world_time, dt, &needs_rewind, &rewind_time);
assert((*q0.m_time_step_info.begin())->getNumberOfEvents() == 2);
// This will be added to timestep 0
q0.addNetworkEvent(dummy_rewinder, NULL, 0.2f);
dt = 0.01f; // to small, event from 0.2 will not be merged
q0.mergeNetworkData(world_time, dt, &needs_rewind, &rewind_time);
assert(q0.m_time_step_info.size() == 2);
assert((*q0.m_time_step_info.begin())->getNumberOfEvents() == 2);
dt = 0.3f;
q0.mergeNetworkData(world_time, dt, &needs_rewind, &rewind_time);
assert(q0.m_time_step_info.size() == 2);
assert((*q0.m_time_step_info.begin())->getNumberOfEvents() == 3);
// This event will get added to the last time step info at 1.0:
q0.addNetworkEvent(dummy_rewinder, NULL, 1.0f);
world_time = 0.8f;
dt = 0.3f;
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);
// 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.addNewTimeStep(1.0f, 0.1f);
++b1; // Should now point at end of list
b1.hasMoreRewindInfo();
b1.addNewTimeStep(2.0f, 0.1f);
TimeStepInfo *tsi = b1.getCurrent();
assert(tsi->getTime() == 2.0f);
} // unitTesting

118
src/network/rewind_queue.hpp Executable file
View 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 EventRewinder;
class RewindInfo;
class TimeStepInfo;
/** \ingroup network
*/
class RewindQueue
{
private:
/** Pointer to all saved */
typedef std::list<TimeStepInfo*> AllTimeStepInfo;
/** The list of all events that are affected by a rewind. */
AllTimeStepInfo m_time_step_info;
/** The list of all events received from the network. They are stored
* in a separate thread (so this data structure is thread-save), and
* merged into m_rewind_info from the main thread. This design (as
* opposed to locking m_rewind_info) reduces the synchronisation
* between main thread and network thread. */
typedef std::vector<RewindInfo*> AllNetworkRewindInfo;
Synchronised<AllNetworkRewindInfo> m_network_events;
/** Iterator to the curren time step info to be handled. This should
* always be at the same time as World::getTime(). */
AllTimeStepInfo::iterator m_current;
AllTimeStepInfo::iterator findPreviousTimeStepInfo(float t);
void insertRewindInfo(RewindInfo *ri);
struct _TimeStepInfoCompare
{
bool operator()(const TimeStepInfo * const ri1, const TimeStepInfo * const ri2) const;
} m_time_step_info_compare;
void testingSortingOrderType(EventRewinder *rewinder, int types[3]);
void testingSortingOrderTime(EventRewinder *rewinder, int types[3],
float times[3] );
public:
static void unitTesting();
RewindQueue();
~RewindQueue();
void reset();
void addNewTimeStep(float time, float dt);
void addLocalEvent(EventRewinder *event_rewinder, BareNetworkString *buffer,
bool confirmed, float time);
void addLocalState(Rewinder *rewinder, BareNetworkString *buffer,
bool confirmed, float time);
void addNetworkEvent(EventRewinder *event_rewinder,
BareNetworkString *buffer, float time);
void addNetworkState(Rewinder *rewinder, BareNetworkString *buffer,
float time, float dt);
void mergeNetworkData(float world_time, float dt,
bool *needs_rewind, float *rewind_time);
bool isEmpty() const;
bool hasMoreRewindInfo() const;
void undoUntil(float undo_time);
float determineNextDT(float max_time);
// ------------------------------------------------------------------------
/** Returns the last (i.e. newest) entry in the TimeStepInfo list. This is
* used for rewinds, since it's the first TimeStep that must not be
* rewound. */
TimeStepInfo *getLast() { return *m_time_step_info.rbegin(); }
// ------------------------------------------------------------------------
RewindQueue::AllTimeStepInfo::iterator& operator++()
{
assert(m_current != m_time_step_info.end());
m_current++;
return m_current;
} // operator++
// ------------------------------------------------------------------------
/** Returns the current RewindInfo. Caller must make sure that there is at least
* one more RewindInfo (see hasMoreRewindInfo()). */
TimeStepInfo *getCurrent()
{
assert(m_current != m_time_step_info.end());
return *m_current;
} // getNext
}; // RewindQueue
#endif

View File

@ -24,11 +24,26 @@ class BareNetworkString;
class Rewinder
{
private:
/** True if this object can be destroyed, i.e. if this object is a 'stand
* alone' (i.e. not used in inheritance). If the object is used in
* inheritance (e.g. KartRewinder, which is a Rewinder and Kart), then
* freeing the kart will free this rewinder instance as well.
*/
bool m_can_be_destroyed;
public:
Rewinder(bool can_be_destroyed);
virtual ~Rewinder();
/** Called before a rewind. Is used to save the previous position of an
* object before a rewind, so that the error due to a rewind can be
* computed. */
virtual void saveTransform() = 0;
/** Called when a rewind is finished, and is used to compute the error
* caused by the rewind (which is then visually smoothed over time). */
virtual void computeError() = 0;
/** 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.

View File

@ -16,7 +16,7 @@
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "network/server.hpp"
#include "network/network_config.hpp"
#include "io/xml_node.hpp"
#include "utils/constants.hpp"
#include "utils/string_utils.hpp"
@ -26,9 +26,8 @@ Server::SortOrder Server::m_sort_order = Server::SO_NAME;
/** Constructor based on XML data received from the stk server.
* \param xml The data for one server as received as part of the
* get-all stk-server request.
* \param is_lan If this is a lan only server.
*/
Server::Server(const XMLNode & xml, bool is_lan)
Server::Server(const XMLNode& xml)
{
assert(xml.getName() == "server");
@ -37,9 +36,12 @@ Server::Server(const XMLNode & xml, bool is_lan)
m_server_id = 0;
m_current_players = 0;
m_max_players = 0;
m_is_lan = is_lan;
m_minor_mode = RaceManager::MINOR_MODE_NORMAL_RACE;
m_difficulty = RaceManager::DIFFICULTY_HARD;
unsigned server_data = 0;
xml.get("game_mode", &server_data);
m_minor_mode = NetworkConfig::get()->getLocalGameMode(server_data).first;
m_major_mode = NetworkConfig::get()->getLocalGameMode(server_data).second;
xml.get("difficulty", &server_data);
m_difficulty = (RaceManager::Difficulty)server_data;
xml.get("name", &m_lower_case_name);
m_name = StringUtils::xmlDecode(m_lower_case_name);
@ -60,29 +62,32 @@ Server::Server(const XMLNode & xml, bool is_lan)
// ----------------------------------------------------------------------------
/** Manual server creation, based on data received from a LAN server discovery
* (see ServersManager::getLANRefresh). This constructor is only used for
* LAN servers.
* (see ServersManager::getLANRefresh) or local graphics server creation
* where the server info is known already.
* \param server_id ID of server.
* \param name Name of the server.
* \param is_lan If this is a lan-only server.
* \param max_players Maximum number of players allowed on this server.
* \param current_players The currently connected number of players.
* \param difficulty The difficulty of server.
* \param server_mode The game modes of server (including minor and major).
* \param address IP and port of the server.
*/
Server::Server(const core::stringw &name, bool is_lan, int max_players,
int current_players, const TransportAddress &address)
Server::Server(unsigned server_id, const core::stringw &name, int max_players,
int current_players, unsigned difficulty, unsigned server_mode,
const TransportAddress &address)
{
m_name = name;
m_satisfaction_score = 0;
m_server_id = 0;
m_server_id = server_id;
m_current_players = current_players;
m_max_players = max_players;
m_is_lan = is_lan;
m_address.copy(address);
// In case of LAN server, public and private port are the same.
m_private_port = m_address.getPort();
m_minor_mode = RaceManager::MINOR_MODE_NORMAL_RACE;
m_difficulty = RaceManager::DIFFICULTY_HARD;
} // server(name, ...)
m_difficulty = (RaceManager::Difficulty)difficulty;
m_minor_mode = NetworkConfig::get()->getLocalGameMode(server_mode).first;
m_major_mode = NetworkConfig::get()->getLocalGameMode(server_mode).second;
} // server(server_id, ...)
// ----------------------------------------------------------------------------
/** \brief Filter the add-on with a list of words.

View File

@ -44,9 +44,12 @@ public:
/** Set the sort order used in the comparison function. */
enum SortOrder
{
SO_SCORE = 1, // Sorted on satisfaction score
SO_NAME = 2, // Sorted alphabetically by name
SO_PLAYERS = 4
SO_NAME = 0, // Sorted alphabetically by name
SO_PLAYERS = 1,
SO_DIFFICULTY = 2,
SO_GAME_MODE = 3,
SO_SCORE = 4 // Sorted on satisfaction score (unused)
};
protected:
@ -81,6 +84,8 @@ protected:
RaceManager::MinorRaceModeType m_minor_mode;
RaceManager::MajorRaceModeType m_major_mode;
RaceManager::Difficulty m_difficulty;
/** The sort order to be used in the comparison. */
@ -89,9 +94,10 @@ protected:
public:
/** Initialises the object from an XML node. */
Server(const XMLNode &xml, bool is_lan);
Server(const irr::core::stringw &name, bool is_lan, int max_players,
int current_players, const TransportAddress &address);
Server(const XMLNode &xml);
Server(unsigned server_id, const irr::core::stringw &name,
int max_players, int current_players, unsigned difficulty,
unsigned server_mode, const TransportAddress &address);
bool filterByWords(const irr::core::stringw words) const;
// ------------------------------------------------------------------------
/** Returns ip address and port of this server. */
@ -117,13 +123,13 @@ public:
/** Returns the number of currently connected players. */
const int getCurrentPlayers() const { return m_current_players; }
// ------------------------------------------------------------------------
RaceManager::MinorRaceModeType getRaceMinorMode() const { return m_minor_mode; }
RaceManager::MinorRaceModeType getRaceMinorMode() const
{ return m_minor_mode; }
// ------------------------------------------------------------------------
void setRaceMinorMode(RaceManager::MinorRaceModeType m) { m_minor_mode = m; }
RaceManager::MajorRaceModeType getRaceMajorMode() const
{ return m_major_mode; }
// ------------------------------------------------------------------------
RaceManager::Difficulty getDifficulty() const { return m_difficulty; }
// ------------------------------------------------------------------------
void setDifficulty(RaceManager::Difficulty d) { m_difficulty = d; }
RaceManager::Difficulty getDifficulty() const { return m_difficulty; }
// ------------------------------------------------------------------------
/** Compares two servers according to the sort order currently defined.
* \param a The addon to compare this addon to.
@ -142,6 +148,12 @@ public:
case SO_PLAYERS:
return m_current_players < server.m_current_players;
break;
case SO_DIFFICULTY:
return m_difficulty < server.m_difficulty;
break;
case SO_GAME_MODE:
return m_minor_mode < server.m_minor_mode;
break;
} // switch
return true;

View File

@ -169,6 +169,8 @@ Online::XMLRequest* ServersManager::getLANRefreshRequest() const
// any local servers.
double start_time = StkTime::getRealTime();
const double DURATION = 1.0;
int cur_server_id = ServersManager::get()->getNumServers();
assert(cur_server_id == 0);
while(StkTime::getRealTime() - start_time < DURATION)
{
TransportAddress sender;
@ -183,16 +185,11 @@ Online::XMLRequest* ServersManager::getLANRefreshRequest() const
uint8_t players = s.getUInt8();
uint32_t my_ip = s.getUInt32();
uint16_t my_port = s.getUInt16();
uint16_t mode = s.getUInt16();
uint8_t difficulty = s.getUInt8();
Server* server = new Server(name, /*lan*/true,
max_players, players, sender);
server->setDifficulty((RaceManager::Difficulty)difficulty);
server->setRaceMinorMode((RaceManager::MinorRaceModeType)mode);
uint8_t mode = s.getUInt8();
Server* server = new Server(cur_server_id++, name,
max_players,players, difficulty, mode, sender);
ServersManager::get()->addServer(server);
TransportAddress me(my_ip, my_port);
NetworkConfig::get()->setMyAddress(me);
m_success = true;
} // if received_data
} // while still waiting
@ -262,7 +259,7 @@ void ServersManager::refresh(bool success, const XMLNode *input)
const XMLNode *servers_xml = input->getNode("servers");
for (unsigned int i = 0; i < servers_xml->getNumNodes(); i++)
{
addServer(new Server(*servers_xml->getNode(i), /*is_lan*/false));
addServer(new Server(*servers_xml->getNode(i)));
}
m_last_load_time.setAtomic((float)StkTime::getRealTime());
} // refresh

View File

@ -48,7 +48,6 @@ private:
Synchronised<float> m_last_load_time;
void refresh(bool success, const XMLNode * input);
void cleanUpServers();
Online::XMLRequest * getWANRefreshRequest() const;
Online::XMLRequest * getLANRefreshRequest() const;
@ -56,7 +55,7 @@ public:
// Singleton
static ServersManager* get();
static void deallocate();
void cleanUpServers();
Online::XMLRequest * getRefreshRequest(bool request_now = true);
void setJoinedServer(uint32_t server_id);
void unsetJoinedServer();

View File

@ -31,6 +31,7 @@
#include "network/servers_manager.hpp"
#include "network/stk_peer.hpp"
#include "utils/log.hpp"
#include "utils/separate_process.hpp"
#include "utils/time.hpp"
#include "utils/vs.hpp"
@ -43,13 +44,29 @@
# include <errno.h>
# include <sys/socket.h>
#endif
#include <pthread.h>
#include <signal.h>
#ifdef __MINGW32__
# undef _WIN32_WINNT
# define _WIN32_WINNT 0x501
#endif
#ifdef WIN32
# include <winsock2.h>
# include <ws2tcpip.h>
#else
# include <netdb.h>
#endif
#include <sys/types.h>
#include <algorithm>
#include <functional>
#include <random>
#include <string>
STKHost *STKHost::m_stk_host = NULL;
bool STKHost::m_enable_console = false;
void STKHost::create()
void STKHost::create(SeparateProcess* p)
{
assert(m_stk_host == NULL);
if (NetworkConfig::get()->isServer())
@ -59,6 +76,7 @@ void STKHost::create()
Server *server = ServersManager::get()->getJoinedServer();
m_stk_host = new STKHost(server->getServerId(), 0);
}
m_stk_host->m_separate_process = p;
if (!m_stk_host->m_network)
{
delete m_stk_host;
@ -70,7 +88,7 @@ void STKHost::create()
/** \class STKHost
* \brief Represents the local host. It is the main managing point for
* networking. It is responsible for sending and receiving messages,
* and keeping track of onnected peers. It also provides some low
* and keeping track of connected peers. It also provides some low
* level socket functions (i.e. to avoid that enet adds its headers
* to messages, useful for broadcast in LAN and for stun). It can be
* either instantiated as server, or as client.
@ -141,7 +159,7 @@ void STKHost::create()
*
* Server:
*
* The ServerLobby (SLR) will then detect the above client
* The ServerLobbyProtocol (SLP) will then detect the above client
* requests, and start a ConnectToPeer protocol for each incoming client.
* The ConnectToPeer protocol uses:
* 1. GetPeerAddress to get the ip address and port of the client.
@ -151,7 +169,7 @@ void STKHost::create()
* destination (unless if it is a LAN connection, then UDP
* broadcasts will be used).
*
* Each client will run a ClientLobbyProtocol (CLR) to handle the further
* Each client will run a ClientLobbyProtocol (CLP) to handle the further
* interaction with the server. The client will first request a connection
* with the server (this is for the 'logical' connection to the server; so
* far it was mostly about the 'physical' connection, i.e. being able to send
@ -163,8 +181,8 @@ void STKHost::create()
* sent by protocol X on the server will be received by protocol X on the
* client and vice versa. The only exception are the client- and server-lobby:
* They share the same id (set in LobbyProtocol), so a message sent by
* the SLR will be received by the CLR, and a message from the CLR will be
* received by the SLR.
* the SLP will be received by the CLP, and a message from the CLP will be
* received by the SLP.
*
* The server will reply with either a reject message (e.g. too many clients
* already connected), or an accept message. The accept message will contain
@ -179,17 +197,17 @@ void STKHost::create()
* of all connected clients. This information is stored in an array of
* NetworkPlayerProfile managed in GameSetup (which is stored in STKHost).
*
* When the authorised clients starts the kart selection, the SLR
* informs all clients to start the kart selection (SLR::startSelection).
* When the authorised clients starts the kart selection, the SLP
* informs all clients to start the kart selection (SLP::startSelection).
* This triggers the creation of the kart selection screen in
* CLR::startSelection / CLR::update for all clients. The clients create
* CLP::startSelection / CLP::update for all clients. The clients create
* the ActivePlayer object (which stores which device is used by which
* player). The kart selection in a client calls
* (NetworkKartSelection::playerConfirm) which calls CLR::requestKartSelection.
* This sends a message to SLR::kartSelectionRequested, which verifies the
* (NetworkKartSelection::playerConfirm) which calls CLP::requestKartSelection.
* This sends a message to SLP::kartSelectionRequested, which verifies the
* selected kart and sends this information to all clients (including the
* client selecting the kart in the first place). This message is handled
* by CLR::kartSelectionUpdate. Server and all clients store this information
* by CLP::kartSelectionUpdate. Server and all clients store this information
* in the NetworkPlayerProfile for the corresponding player, so server and
* all clients now have identical information about global player id, player
* name and selected kart. The authorised client will set some default votes
@ -198,9 +216,9 @@ void STKHost::create()
*
* After selecting a kart, the track selection screen is shown. On selecting
* a track, a vote for the track is sent to the client
* (TrackScreen::eventCallback, using CLR::voteTrack). The server will send
* all votes (track, #laps, ...) to all clients (see e.g. SLR::playerTrackVote
* etc), which are handled in e.g. CLR::playerTrackVote().
* (TrackScreen::eventCallback, using CLP::voteTrack). The server will send
* all votes (track, #laps, ...) to all clients (see e.g. SLP::playerTrackVote
* etc), which are handled in e.g. CLP::playerTrackVote().
*
* --> Server and all clients have identical information about all votes
* stored in RaceConfig of GameSetup.
@ -230,7 +248,7 @@ void STKHost::create()
* at that stage as well.
*
* Once the countdown is 0 (or below), the Synchronization Protocol will
* start the protocols: KartUpdateProtocol, ControllerEventsProtocol,
* start the protocols: KartUpdateProtocol, GameProtocol,
* GameEventsProtocol. Then the LatencyProtocol is terminated
* which indicates to the main loop to start the actual game.
*/
@ -245,21 +263,22 @@ STKHost::STKHost(uint32_t server_id, uint32_t host_id)
// server is made.
m_host_id = 0;
init();
TransportAddress a;
a.setIP(0);
a.setPort(NetworkConfig::get()->getClientPort());
ENetAddress ea = a.toEnetAddress();
ENetAddress ea;
ea.host = STKHost::HOST_ANY;
ea.port = NetworkConfig::get()->getClientPort();
m_network = new Network(/*peer_count*/1, /*channel_limit*/2,
/*max_in_bandwidth*/0, /*max_out_bandwidth*/0, &ea);
/*max_in_bandwidth*/0, /*max_out_bandwidth*/0,
&ea, true/*change_port_if_bound*/);
if (!m_network)
{
Log::fatal ("STKHost", "An error occurred while trying to create "
"an ENet client host.");
}
Protocol *connect = new ConnectToServer(server_id, host_id);
connect->requestStart();
setPrivatePort();
std::make_shared<ConnectToServer>(server_id, host_id)->requestStart();
} // STKHost
// ----------------------------------------------------------------------------
@ -281,16 +300,17 @@ STKHost::STKHost(const irr::core::stringw &server_name)
m_network= new Network(NetworkConfig::get()->getMaxPlayers(),
/*channel_limit*/2,
/*max_in_bandwidth*/0,
/*max_out_bandwidth*/ 0, &addr);
/*max_out_bandwidth*/ 0, &addr,
true/*change_port_if_bound*/);
if (!m_network)
{
Log::fatal("STKHost", "An error occurred while trying to create an "
"ENet server host.");
}
startListening();
Protocol *p = LobbyProtocol::create<ServerLobby>();
ProtocolManager::getInstance()->requestStart(p);
setPrivatePort();
ProtocolManager::lock()
->requestStart(LobbyProtocol::create<ServerLobby>());
} // STKHost(server_name)
@ -302,13 +322,10 @@ void STKHost::init()
{
m_shutdown = false;
m_network = NULL;
m_lan_network = NULL;
m_listening_thread = NULL;
m_game_setup = NULL;
m_is_registered = false;
m_error_message = "";
pthread_mutex_init(&m_exit_mutex, NULL);
m_exit_flag.clear();
m_exit_flag.test_and_set();
// Start with initialising ENet
// ============================
@ -320,15 +337,14 @@ void STKHost::init()
Log::info("STKHost", "Host initialized.");
Network::openLog(); // Open packet log file
ProtocolManager::getInstance<ProtocolManager>();
ProtocolManager::createInstance();
// Optional: start the network console
m_network_console = NULL;
if(m_enable_console)
if (m_enable_console)
{
m_network_console = new NetworkConsole();
m_network_console->run();
}
m_network_console = std::thread(std::bind(&NetworkConsole::mainLoop,
this));
}
} // STKHost
// ----------------------------------------------------------------------------
@ -337,7 +353,9 @@ void STKHost::init()
*/
STKHost::~STKHost()
{
ProtocolManager::kill();
requestShutdown();
if (m_network_console.joinable())
m_network_console.join();
// delete the game setup
if (m_game_setup)
delete m_game_setup;
@ -354,20 +372,10 @@ STKHost::~STKHost()
stopListening();
delete m_network;
enet_deinitialize();
delete m_separate_process;
} // ~STKHost
//-----------------------------------------------------------------------------
/** Requests that the network infrastructure is to be shut down. This function
* is called from a thread, but the actual shutdown needs to be done from
* the main thread to avoid race conditions (e.g. ProtocolManager might still
* access data structures when the main thread tests if STKHost exist (which
* it does, but ProtocolManager might be shut down already.
*/
void STKHost::requestShutdown()
{
m_shutdown = true;
} // requestExit
//-----------------------------------------------------------------------------
/** Called from the main thread when the network infrastructure is to be shut
* down.
@ -375,11 +383,216 @@ void STKHost::requestShutdown()
void STKHost::shutdown()
{
ServersManager::get()->unsetJoinedServer();
ProtocolManager::getInstance()->abort();
ProtocolManager::lock()->abort();
deleteAllPeers();
destroy();
} // shutdown
//-----------------------------------------------------------------------------
/** Set the public address using stun protocol.
*/
void STKHost::setPublicAddress()
{
std::vector<std::string> untried_server = UserConfigParams::m_stun_servers;
// Generate random list of stun servers
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(untried_server.begin(), untried_server.end(), g);
while (!untried_server.empty())
{
// Pick last element in untried servers
const char* server_name = untried_server.back().c_str();
Log::debug("STKHost", "Using STUN server %s", server_name);
struct addrinfo hints, *res;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
hints.ai_socktype = SOCK_STREAM;
// Resolve the stun server name so we can send it a STUN request
int status = getaddrinfo(server_name, NULL, &hints, &res);
if (status != 0)
{
Log::error("STKHost", "Error in getaddrinfo for stun server"
" %s: %s", server_name, gai_strerror(status));
untried_server.pop_back();
continue;
}
untried_server.pop_back();
// documentation says it points to "one or more addrinfo structures"
assert(res != NULL);
struct sockaddr_in* current_interface = (struct sockaddr_in*)(res->ai_addr);
m_stun_address.setIP(ntohl(current_interface->sin_addr.s_addr));
m_stun_address.setPort(3478);
// Assemble the message for the stun server
BareNetworkString s(20);
constexpr uint32_t magic_cookie = 0x2112A442;
// bytes 0-1: the type of the message
// bytes 2-3: message length added to header (attributes)
uint16_t message_type = 0x0001; // binding request
uint16_t message_length = 0x0000;
s.addUInt16(message_type).addUInt16(message_length)
.addUInt32(magic_cookie);
uint8_t stun_tansaction_id[12];
// bytes 8-19: the transaction id
for (int i = 0; i < 12; i++)
{
uint8_t random_byte = rand() % 256;
s.addUInt8(random_byte);
stun_tansaction_id[i] = random_byte;
}
m_network->sendRawPacket(s, m_stun_address);
freeaddrinfo(res);
// Recieve now
TransportAddress sender;
const int LEN = 2048;
char buffer[LEN];
int len = m_network->receiveRawPacket(buffer, LEN, &sender, 2000);
if (sender.getIP() != m_stun_address.getIP())
{
Log::warn("STKHost",
"Received stun response from %s instead of %s.",
sender.toString().c_str(), m_stun_address.toString().c_str());
}
if (len <= 0)
{
Log::error("STKHost", "STUN response contains no data at all");
continue;
}
// Convert to network string.
BareNetworkString response(buffer, len);
if (response.size() < 20)
{
Log::error("STKHost", "STUN response should be at least 20 bytes.");
continue;
}
if (response.getUInt16() != 0x0101)
{
Log::error("STKHost", "STUN has no binding success response.");
continue;
}
// Skip message size
response.getUInt16();
if (response.getUInt32() != magic_cookie)
{
Log::error("STKHost", "STUN response doesn't contain the magic "
"cookie");
continue;
}
for (int i = 0; i < 12; i++)
{
if (response.getUInt8() != stun_tansaction_id[i])
{
Log::error("STKHost", "STUN response doesn't contain the "
"transaction ID");
continue;
}
}
Log::debug("GetPublicAddress",
"The STUN server responded with a valid answer");
// The stun message is valid, so we parse it now:
// Those are the port and the address to be detected
bool found = false;
while (true)
{
if (response.size() < 4)
{
Log::error("STKHost", "STUN response is invalid.");
break;
}
unsigned type = response.getUInt16();
unsigned size = response.getUInt16();
// Bit determining whether comprehension of an attribute is optional.
// Described in section 15 of RFC 5389.
constexpr uint16_t comprehension_optional = 0x1 << 15;
// Bit determining whether the bit was assigned by IETF Review.
// Described in section 18.1. of RFC 5389.
constexpr uint16_t IETF_review = 0x1 << 14;
// Defined in section 15.1 of RFC 5389
constexpr uint8_t ipv4 = 0x01;
// Defined in section 18.2 of RFC 5389
constexpr uint16_t mapped_address = 0x001;
constexpr uint16_t xor_mapped_address = 0x0020;
// The first two bits are irrelevant to the type
type &= ~(comprehension_optional | IETF_review);
if (type == mapped_address || type == xor_mapped_address)
{
if (size != 8 || response.size() < 8)
{
Log::error("STKHost", "Invalid STUN mapped address "
"length");
break;
}
// Ignore the first byte as mentioned in Section 15.1 of RFC
// 5389.
uint8_t ip_type = response.getUInt8();
ip_type = response.getUInt8();
if (ip_type != ipv4)
{
Log::error("STKHost", "Only IPv4 is supported");
break;
}
uint16_t port = response.getUInt16();
uint32_t ip = response.getUInt32();
if (type == xor_mapped_address)
{
// Obfuscation is described in Section 15.2 of RFC 5389.
port ^= magic_cookie >> 16;
ip ^= magic_cookie;
}
m_public_address.setPort(port);
m_public_address.setIP(ip);
found = true;
break;
} // type == mapped_address || type == xor_mapped_address
else
{
response.skip(size);
int padding = size % 4;
if (padding != 0)
response.skip(4 - padding);
}
} // while true
// Found public address and port
if (found)
untried_server.clear();
}
} // setPublicAddress
//-----------------------------------------------------------------------------
void STKHost::setPrivatePort()
{
struct sockaddr_in sin;
socklen_t len = sizeof(sin);
ENetHost *host = m_network->getENetHost();
if (getsockname(host->socket, (struct sockaddr *)&sin, &len) == -1)
{
Log::error("STKHost", "Error while using getsockname().");
m_private_port = 0;
}
else
m_private_port = ntohs(sin.sin_port);
} // setPrivatePort
//-----------------------------------------------------------------------------
/** A previous GameSetup is deletea and a new one is created.
* \return Newly create GameSetup object.
@ -414,7 +627,7 @@ void STKHost::abort()
{
// Finish protocol manager first, to avoid that it access data
// in STKHost.
ProtocolManager::getInstance()->abort();
ProtocolManager::lock()->abort();
stopListening();
} // abort
@ -423,18 +636,14 @@ void STKHost::abort()
*/
void STKHost::setErrorMessage(const irr::core::stringw &message)
{
irr::core::stringc s(message.c_str());
Log::error("STKHost", "%s", s.c_str());
if (!message.empty())
{
irr::core::stringc s(message.c_str());
Log::error("STKHost", "%s", s.c_str());
}
m_error_message = message;
} // setErrorMessage
// --------------------------------------------------------------------
/** Returns the last error (or "" if no error has happened). */
const irr::core::stringw& STKHost::getErrorMessage() const
{
return m_error_message;
} // getErrorMessage
// --------------------------------------------------------------------
/** \brief Try to establish a connection to a given transport address.
* \param peer : The transport address which you want to connect to.
@ -463,9 +672,9 @@ bool STKHost::connect(const TransportAddress& address)
*/
void STKHost::startListening()
{
pthread_mutex_lock(&m_exit_mutex); // will let the update function start
m_listening_thread = new pthread_t;
pthread_create(m_listening_thread, NULL, &STKHost::mainLoop, this);
m_exit_flag.clear();
m_exit_flag.test_and_set();
m_listening_thread = std::thread(std::bind(&STKHost::mainLoop, this));
} // startListening
// ----------------------------------------------------------------------------
@ -474,29 +683,11 @@ void STKHost::startListening()
*/
void STKHost::stopListening()
{
if (m_listening_thread)
{
// This will stop the update function on its next update
pthread_mutex_unlock(&m_exit_mutex);
pthread_join(*m_listening_thread, NULL); // wait for the thread to end
}
m_exit_flag.clear();
if (m_listening_thread.joinable())
m_listening_thread.join();
} // stopListening
// ---------------------------------------------------------------------------
/** \brief Returns true when the thread should stop listening.
*/
int STKHost::mustStopListening()
{
switch (pthread_mutex_trylock(&m_exit_mutex)) {
case 0: /* if we got the lock, unlock and return 1 (true) */
pthread_mutex_unlock(&m_exit_mutex);
return 1;
case EBUSY: /* return 0 (false) if the mutex was locked */
return 0;
}
return 1;
} // mustStopListening
// ----------------------------------------------------------------------------
/** Returns true if this client instance is allowed to control the server.
* A client can authorise itself by providing the server's password. It is
@ -521,26 +712,34 @@ bool STKHost::isAuthorisedToControl() const
* event and passes it to the Network Manager.
* \param self : used to pass the ENet host to the function.
*/
void* STKHost::mainLoop(void* self)
void STKHost::mainLoop()
{
VS::setThreadName("STKHost");
ENetEvent event;
STKHost* myself = (STKHost*)(self);
ENetHost* host = myself->m_network->getENetHost();
ENetHost* host = m_network->getENetHost();
if(NetworkConfig::get()->isServer() &&
(NetworkConfig::get()->isLAN() || NetworkConfig::get()->isPublicServer()) )
// A separate network connection (socket) to handle LAN requests.
Network* lan_network = NULL;
if ((NetworkConfig::get()->isLAN() && NetworkConfig::get()->isServer()) ||
NetworkConfig::get()->isPublicServer())
{
TransportAddress address(0, NetworkConfig::get()->getServerDiscoveryPort());
TransportAddress address(0,
NetworkConfig::get()->getServerDiscoveryPort());
ENetAddress eaddr = address.toEnetAddress();
myself->m_lan_network = new Network(1, 1, 0, 0, &eaddr);
lan_network = new Network(1, 1, 0, 0, &eaddr);
if (lan_network->getENetHost() == NULL)
{
delete lan_network;
lan_network = NULL;
}
}
while (!myself->mustStopListening())
while (m_exit_flag.test_and_set())
{
if(myself->m_lan_network)
auto sl = LobbyProtocol::get<ServerLobby>();
if (lan_network && sl && sl->waitingForPlayers())
{
myself->handleDirectSocketRequest();
handleDirectSocketRequest(lan_network);
} // if discovery host
while (enet_host_service(host, &event, 20) != 0)
@ -548,40 +747,50 @@ void* STKHost::mainLoop(void* self)
if (event.type == ENET_EVENT_TYPE_NONE)
continue;
auto pm = ProtocolManager::lock();
if (!pm || pm->isExiting())
{
// Don't create more event if no protocol manager or it will
// be exiting
continue;
}
// Create an STKEvent with the event data. This will also
// create the peer if it doesn't exist already
Event* stk_event = new Event(&event);
Log::verbose("STKHost", "Event of type %d received",
(int)(stk_event->getType()));
STKPeer* peer = stk_event->getPeer();
if (stk_event->getType() == EVENT_TYPE_CONNECTED)
{
Log::info("STKHost", "A client has just connected. There are "
"now %lu peers.", myself->m_peers.size());
"now %lu peers.", m_peers.size());
Log::debug("STKHost", "Addresses are : %lx, %lx",
stk_event->getPeer(), peer);
} // EVENT_TYPE_CONNECTED
else if (stk_event->getType() == EVENT_TYPE_DISCONNECTED)
{
Log::info("STKHost", "A client has just disconnected.");
Log::flushBuffers();
} // EVENT_TYPE_CONNECTED
else if (stk_event->getType() == EVENT_TYPE_MESSAGE)
{
Network::logPacket(stk_event->data(), true);
TransportAddress stk_addr(peer->getAddress());
#ifdef DEBUG_MESSAGE_CONTENT
Log::verbose("NetworkManager",
"Message, Sender : %s, message:",
stk_addr.toString(/*show port*/false).c_str());
"Message, Sender : %s time %f message:",
stk_addr.toString(/*show port*/false).c_str(),
StkTime::getRealTime());
Log::verbose("NetworkManager", "%s",
stk_event->data().getLogMessage().c_str());
#endif
} // if message event
// notify for the event now.
ProtocolManager::getInstance()->propagateEvent(stk_event);
} // while enet_host_service
} // while !mustStopListening
pm->propagateEvent(stk_event);
free(myself->m_listening_thread);
myself->m_listening_thread = NULL;
} // while enet_host_service
} // while m_exit_flag.test_and_set()
delete lan_network;
Log::info("STKHost", "Listening has been stopped");
return NULL;
} // mainLoop
// ----------------------------------------------------------------------------
@ -593,13 +802,13 @@ void* STKHost::mainLoop(void* self)
* message is received, will answer with a message containing server details
* (and sender IP address and port).
*/
void STKHost::handleDirectSocketRequest()
void STKHost::handleDirectSocketRequest(Network* lan_network)
{
const int LEN=2048;
char buffer[LEN];
TransportAddress sender;
int len = m_lan_network->receiveRawPacket(buffer, LEN, &sender, 1);
int len = lan_network->receiveRawPacket(buffer, LEN, &sender, 1);
if(len<=0) return;
BareNetworkString message(buffer, len);
std::string command;
@ -624,23 +833,25 @@ void STKHost::handleDirectSocketRequest()
s.addUInt8(0); // FIXME: current number of connected players
s.addUInt32(sender.getIP());
s.addUInt16(sender.getPort());
s.addUInt16((uint16_t)race_manager->getMinorMode());
s.addUInt8((uint8_t)race_manager->getDifficulty());
m_lan_network->sendRawPacket(s, sender);
s.addUInt8((uint8_t)
NetworkConfig::get()->getServerGameMode(race_manager->getMinorMode(),
race_manager->getMajorMode()));
lan_network->sendRawPacket(s, sender);
} // if message is server-requested
else if (command == "connection-request")
{
// In case of a LAN connection, we only allow connections from
// a LAN address (192.168*, ..., and 127.*).
if (NetworkConfig::get()->isLAN() && !sender.isLAN())
if (!sender.isLAN() && !sender.isPublicAddressLAN() &&
!NetworkConfig::get()->isPublicServer())
{
Log::error("STKHost", "Client trying to connect from '%s'",
sender.toString().c_str());
Log::error("STKHost", "which is outside of LAN - rejected.");
return;
}
Protocol *c = new ConnectToPeer(sender);
c->requestStart();
std::make_shared<ConnectToPeer>(sender)->requestStart();
}
else
Log::info("STKHost", "Received unknown command '%s'",
@ -704,6 +915,18 @@ STKPeer* STKHost::getPeer(ENetPeer *enet_peer)
m_next_unique_host_id ++;
return peer;
} // getPeer
// ----------------------------------------------------------------------------
/** \brief Return the only server peer for client.
* \return STKPeer the STKPeer of server.
*/
STKPeer* STKHost::getServerPeerForClient() const
{
assert(m_peers.size() == 1);
assert(NetworkConfig::get()->isClient());
return m_peers[0];
} // getServerPeerForClient
// ----------------------------------------------------------------------------
/** \brief Tells if a peer is known and connected.
* \return True if the peer is known and connected, false elseway.
@ -760,20 +983,6 @@ void STKHost::removePeer(const STKPeer* peer)
m_peers.size());
} // removePeer
//-----------------------------------------------------------------------------
uint16_t STKHost::getPort() const
{
struct sockaddr_in sin;
socklen_t len = sizeof(sin);
ENetHost *host = m_network->getENetHost();
if (getsockname(host->socket, (struct sockaddr *)&sin, &len) == -1)
Log::error("STKHost", "Error while using getsockname().");
else
return ntohs(sin.sin_port);
return 0;
} // getPort
//-----------------------------------------------------------------------------
/** Sends data to all peers except the specified one.
* \param peer Peer which will not receive the message.

View File

@ -38,10 +38,11 @@
#define WIN32_LEAN_AND_MEAN
#include <enet/enet.h>
#include <pthread.h>
#include <atomic>
#include <thread>
class GameSetup;
class NetworkConsole;
class SeparateProcess;
class STKHost
{
@ -63,14 +64,14 @@ private:
/** Singleton pointer to the instance. */
static STKHost* m_stk_host;
/** Separate process of server instance. */
SeparateProcess* m_separate_process;
/** ENet host interfacing sockets. */
Network* m_network;
/** A separate network connection (socket) to handle LAN requests. */
Network *m_lan_network;
/** Network console */
NetworkConsole *m_network_console;
/** Network console thread */
std::thread m_network_console;
/** The list of peers connected to this instance. */
std::vector<STKPeer*> m_peers;
@ -88,28 +89,38 @@ private:
GameSetup* m_game_setup;
/** Id of thread listening to enet events. */
pthread_t* m_listening_thread;
std::thread m_listening_thread;
/** Flag which is set from the protocol manager thread which
* triggers a shutdown of the STKHost (and the Protocolmanager). */
bool m_shutdown;
std::atomic_bool m_shutdown;
/** Mutex used to stop this thread. */
pthread_mutex_t m_exit_mutex;
/** If this is a server, it indicates if this server is registered
* with the stk server. */
bool m_is_registered;
/** Atomic flag used to stop this thread. */
std::atomic_flag m_exit_flag = ATOMIC_FLAG_INIT;
/** An error message, which is set by a protocol to be displayed
* in the GUI. */
irr::core::stringw m_error_message;
/** The public address found by stun (if WAN is used). */
TransportAddress m_public_address;
/** The public address stun server used. */
TransportAddress m_stun_address;
/** The private port enet socket is bound. */
uint16_t m_private_port;
/** An error message, which is set by a protocol to be displayed
* in the GUI. */
STKHost(uint32_t server_id, uint32_t host_id);
STKHost(const irr::core::stringw &server_name);
virtual ~STKHost();
void init();
void handleDirectSocketRequest();
void handleDirectSocketRequest(Network* lan_network);
// ------------------------------------------------------------------------
void mainLoop();
public:
/** If a network console should be started. Note that the console can cause
@ -120,7 +131,7 @@ public:
/** Creates the STKHost. It takes all confifguration parameters from
* NetworkConfig. This STKHost can either be a client or a server.
*/
static void create();
static void create(SeparateProcess* p = NULL);
// ------------------------------------------------------------------------
/** Returns the instance of STKHost. */
@ -140,14 +151,37 @@ public:
/** Checks if the STKHost has been created. */
static bool existHost() { return m_stk_host != NULL; }
// ------------------------------------------------------------------------
static void* mainLoop(void* self);
const TransportAddress& getPublicAddress() const
{ return m_public_address; }
// ------------------------------------------------------------------------
const TransportAddress& getStunAddress() const
{ return m_stun_address; }
// ------------------------------------------------------------------------
uint16_t getPrivatePort() const
{ return m_private_port; }
// ------------------------------------------------------------------------
void setPrivatePort();
// ------------------------------------------------------------------------
void setPublicAddress();
// ------------------------------------------------------------------------
virtual GameSetup* setupNewGame();
void abort();
void deleteAllPeers();
bool connect(const TransportAddress& peer);
void requestShutdown();
//-------------------------------------------------------------------------
/** Requests that the network infrastructure is to be shut down. This
* function is called from a thread, but the actual shutdown needs to be
* done from the main thread to avoid race conditions (e.g.
* ProtocolManager might still access data structures when the main thread
* tests if STKHost exist (which it does, but ProtocolManager might be
* shut down already.
*/
void requestShutdown()
{
m_shutdown.store(true);
} // requestExit
//-------------------------------------------------------------------------
void shutdown();
void sendPacketExcept(STKPeer* peer,
@ -162,82 +196,66 @@ public:
void removePeer(const STKPeer* peer);
bool isConnectedTo(const TransportAddress& peer_address);
STKPeer *getPeer(ENetPeer *enet_peer);
STKPeer *getServerPeerForClient() const;
std::vector<NetworkPlayerProfile*> getMyPlayerProfiles();
int mustStopListening();
uint16_t getPort() const;
void setErrorMessage(const irr::core::stringw &message);
bool isAuthorisedToControl() const;
const irr::core::stringw&
getErrorMessage() const;
// --------------------------------------------------------------------
// ------------------------------------------------------------------------
/** Returns the last error (or "" if no error has happened). */
const irr::core::stringw& getErrorMessage() const
{ return m_error_message; }
// ------------------------------------------------------------------------
/** Returns true if a shutdown of the network infrastructure was
* requested. */
bool requestedShutdown() const { return m_shutdown; }
// --------------------------------------------------------------------
bool requestedShutdown() const { return m_shutdown.load(); }
// ------------------------------------------------------------------------
/** Returns the current game setup. */
GameSetup* getGameSetup() { return m_game_setup; }
// --------------------------------------------------------------------
GameSetup* getGameSetup() { return m_game_setup; }
// ------------------------------------------------------------------------
int receiveRawPacket(char *buffer, int buffer_len,
TransportAddress* sender, int max_tries = -1)
{
return m_network->receiveRawPacket(buffer, buffer_len, sender,
max_tries);
} // receiveRawPacket
// --------------------------------------------------------------------
// ------------------------------------------------------------------------
void sendRawPacket(const BareNetworkString &buffer,
const TransportAddress& dst)
{
m_network->sendRawPacket(buffer, dst);
} // sendRawPacket
// --------------------------------------------------------------------
/** Returns the IP address of this host. */
uint32_t getAddress() const
{
return m_network->getENetHost()->address.host;
} // getAddress
// --------------------------------------------------------------------
// ------------------------------------------------------------------------
/** Returns a const reference to the list of peers. */
const std::vector<STKPeer*> &getPeers() { return m_peers; }
// --------------------------------------------------------------------
const std::vector<STKPeer*> &getPeers() { return m_peers; }
// ------------------------------------------------------------------------
/** Returns the next (unique) host id. */
unsigned int getNextHostId() const
{
assert(m_next_unique_host_id >= 0);
return m_next_unique_host_id;
}
// --------------------------------------------------------------------
// ------------------------------------------------------------------------
/** Returns the number of currently connected peers. */
unsigned int getPeerCount() { return (int)m_peers.size(); }
// --------------------------------------------------------------------
/** Sets if this server is registered with the stk server. */
void setRegistered(bool registered)
{
m_is_registered = registered;
} // setRegistered
// --------------------------------------------------------------------
/** Returns if this server is registered with the stk server. */
bool isRegistered() const
{
return m_is_registered;
} // isRegistered
// --------------------------------------------------------------------
unsigned int getPeerCount() { return (int)m_peers.size(); }
// ------------------------------------------------------------------------
/** Sets the global host id of this host. */
void setMyHostId(uint8_t my_host_id) { m_host_id = my_host_id; }
// --------------------------------------------------------------------
void setMyHostId(uint8_t my_host_id) { m_host_id = my_host_id; }
// ------------------------------------------------------------------------
/** Returns the host id of this host. */
uint8_t getMyHostId() const { return m_host_id; }
// --------------------------------------------------------------------
uint8_t getMyHostId() const { return m_host_id; }
// ------------------------------------------------------------------------
/** Sends a message from a client to the server. */
void sendToServer(NetworkString *data, bool reliable = true)
{
assert(NetworkConfig::get()->isClient());
m_peers[0]->sendPacket(data, reliable);
} // sendToServer
// ------------------------------------------------------------------------
/** True if this is a client and server in graphics mode made by server
* creation screen. */
bool isClientServer() const { return m_separate_process != NULL; }
}; // class STKHost
#endif // STK_HOST_HPP

View File

@ -63,8 +63,8 @@ void STKPeer::sendPacket(NetworkString *data, bool reliable)
{
data->setToken(m_client_server_token);
TransportAddress a(m_enet_peer->address);
Log::verbose("STKPeer", "sending packet of size %d to %s",
data->size(), a.toString().c_str());
Log::verbose("STKPeer", "sending packet of size %d to %s at %f",
data->size(), a.toString().c_str(),StkTime::getRealTime());
ENetPacket* packet = enet_packet_create(data->getData(),
data->getTotalSize(),

View File

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

View File

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

View File

@ -18,8 +18,15 @@
#include "network/transport_address.hpp"
#ifdef WIN32
# include <iphlpapi.h>
#else
#include <ifaddrs.h>
#endif
// ----------------------------------------------------------------------------
/** Returns if this IP address belongs to a LAN, i.e. is in 192.168* or
* 10*, 172.16-31.*, or is on the same host, i.e. 127*.
* 10*, 172.16-31.*, or is on the same host, i.e. 127* or same public address.
*/
bool TransportAddress::isLAN() const
{
@ -33,6 +40,60 @@ bool TransportAddress::isLAN() const
else if (ip >> 24 == 0x7f ) // 127.* localhost
return true;
return false;
}
// ----------------------------------------------------------------------------
/** Returns this IP address is localhost (127.0.0.1).
*/
bool TransportAddress::isPublicAddressLAN() const
{
#ifndef WIN32
struct ifaddrs *ifap, *ifa;
struct sockaddr_in *sa;
getifaddrs(&ifap); // get the info
for (ifa = ifap; ifa; ifa = ifa->ifa_next)
{
if (ifa->ifa_addr->sa_family == AF_INET)
{
sa = (struct sockaddr_in *) ifa->ifa_addr;
// This interface is ours
if (ntohl(sa->sin_addr.s_addr) == getIP())
return true;
}
}
freeifaddrs(ifap);
#else
// Query the list of all IP addresses on the local host. First call to
// GetIpAddrTable with 0 bytes buffer will return insufficient buffer
// error, and size will contain the number of bytes needed for all data.
// Repeat the process of querying the size using GetIpAddrTable in a while
// loop since it can happen that an interface comes online between the
// previous call to GetIpAddrTable and the next call.
MIB_IPADDRTABLE *table = NULL;
unsigned long size = 0;
int error = GetIpAddrTable(table, &size, 0);
// Also add a count to limit the while loop - in case that something
// strange is going on.
int count = 0;
while (error == ERROR_INSUFFICIENT_BUFFER && count < 10)
{
delete[] table; // deleting NULL is legal
table = (MIB_IPADDRTABLE*)new char[size];
error = GetIpAddrTable(table, &size, 0);
count++;
} // while insufficient buffer
for (unsigned int i = 0; i < table->dwNumEntries; i++)
{
unsigned int ip = ntohl(table->table[i].dwAddr);
if (getIP() == ip) // this interface is ours
{
delete[] table;
return true;
}
}
delete[] table;
#endif
return false;
} // isLAN
// ----------------------------------------------------------------------------
@ -42,71 +103,71 @@ bool TransportAddress::isLAN() const
void TransportAddress::unitTesting()
{
TransportAddress t1("192.168.0.0");
assert(t1.getIP() == (192 << 24) + (168 << 16));
assert(t1.getIP() == (192u << 24) + (168u << 16));
assert(t1.isLAN());
TransportAddress t2("192.168.255.255");
assert(t2.getIP() == (192 << 24) + (168 << 16) + (255 << 8) + 255);
assert(t2.getIP() == (192u << 24) + (168u << 16) + (255u << 8) + 255u);
assert(t2.isLAN());
TransportAddress t3("193.168.0.1");
assert(t3.getIP() == (193 << 24) + (168 << 16) + 1);
assert(t3.getIP() == (193u << 24) + (168u << 16) + 1);
assert(!t3.isLAN());
TransportAddress t4("192.167.255.255");
assert(t4.getIP() == (192 << 24) + (167 << 16) + (255 << 8) + 255);
assert(t4.getIP() == (192u << 24) + (167u << 16) + (255u << 8) + 255u);
assert(!t4.isLAN());
TransportAddress t5("192.169.0.0");
assert(t5.getIP() == (192 << 24) + (169 << 16));
assert(t5.getIP() == (192u << 24) + (169u << 16));
assert(!t5.isLAN());
TransportAddress t6("172.16.0.0");
assert(t6.getIP() == (172 << 24) + (16 << 16));
assert(t6.getIP() == (172u << 24) + (16u << 16));
assert(t6.isLAN());
TransportAddress t7("172.31.255.255");
assert(t7.getIP() == (172 << 24) + (31 << 16) + (255 << 8) + 255);
assert(t7.getIP() == (172u << 24) + (31u << 16) + (255u << 8) + 255u);
assert(t7.isLAN());
TransportAddress t8("172.15.255.255");
assert(t8.getIP() == (172 << 24) + (15 << 16) + (255 << 8) + 255);
assert(t8.getIP() == (172u << 24) + (15u << 16) + (255u << 8) + 255u);
assert(!t8.isLAN());
TransportAddress t9("172.32.0.0");
assert(t9.getIP() == (172 << 24) + (32 << 16));
assert(t9.getIP() == (172u << 24) + (32u << 16));
assert(!t9.isLAN());
TransportAddress t10("10.0.0.0");
assert(t10.getIP() == (10 << 24));
assert(t10.getIP() == (10u << 24));
assert(t10.isLAN());
TransportAddress t11("10.255.255.255");
assert(t11.getIP() == (10 << 24) + (255 << 16) + (255 << 8) + 255);
assert(t11.getIP() == (10u << 24) + (255u << 16) + (255u << 8) + 255u);
assert(t11.isLAN());
TransportAddress t12("9.255.255.255");
assert(t12.getIP() == (9 << 24) + (255 << 16) + (255 << 8) + 255);
assert(t12.getIP() == (9u << 24) + (255u << 16) + (255u << 8) + 255u);
assert(!t12.isLAN());
TransportAddress t13("11.0.0.0");
assert(t13.getIP() == (11 << 24) );
assert(t13.getIP() == (11u << 24));
assert(!t13.isLAN());
TransportAddress t14("127.0.0.0");
assert(t14.getIP() == (127 << 24));
assert(t14.getIP() == (127u << 24));
assert(t14.isLAN());
TransportAddress t15("127.255.255.255");
assert(t15.getIP() == (127 << 24) + (255 << 16) + (255 << 8) + 255);
assert(t15.getIP() == (127u << 24) + (255u << 16) + (255u << 8) + 255u);
assert(t15.isLAN());
TransportAddress t16("126.255.255.255");
assert(t16.getIP() == (126 << 24) + (255 << 16) + (255 << 8) + 255);
assert(t16.getIP() == (126u << 24) + (255u << 16) + (255u << 8) + 255u);
assert(!t16.isLAN());
TransportAddress t17("128.0.0.0");
assert(t17.getIP() == (128 << 24));
assert(t17.isLAN());
assert(t17.getIP() == (128u << 24));
assert(!t17.isLAN());
} // unitTesting
} // unitTesting

View File

@ -88,8 +88,13 @@ private:
copy(other);
} // TransportAddress(const TransportAddress&)
public:
// ------------------------------------------------------------------------
bool isPublicAddressLAN() const;
// ------------------------------------------------------------------------
bool isLAN() const;
// ------------------------------------------------------------------------
bool isUnset() const { return m_ip == 0 || m_port == 0; }
// ------------------------------------------------------------------------
/** A copy function (to replace the copy constructor which is disabled
* using NoCopy): it copies the data from the argument into this object.*/
void copy(const TransportAddress &other)

View File

@ -133,7 +133,7 @@ namespace Online
void addParameter(const std::string &name,
const irr::core::stringw &value)
{
core::stringc s = core::stringc(value.c_str());
std::string s = StringUtils::xmlEncode(value);
// Call the template to escape strings properly
addParameter(name, s.c_str());

View File

@ -23,6 +23,7 @@
#include "config/user_config.hpp"
#include "guiengine/message_queue.hpp"
#include "guiengine/screen.hpp"
#include "network/network_config.hpp"
#include "online/online_profile.hpp"
#include "online/profile_manager.hpp"
#include "states_screens/main_menu_screen.hpp"
@ -208,9 +209,18 @@ namespace Online
core::stringw username("");
uint32_t userid(0);
#ifdef DEBUG
int token_fetched = input->get("token", &m_token);
int username_fetched = input->get("username", &username);
int userid_fetched = input->get("userid", &userid);
assert(token_fetched && username_fetched && userid_fetched);
#else
input->get("token", &m_token);
input->get("username", &username);
input->get("userid", &userid);
#endif
NetworkConfig::get()->setCurrentUserId(userid);
NetworkConfig::get()->setCurrentUserToken(m_token);
setLastOnlineName(username);
OnlineProfile* profile = new OnlineProfile(userid, username, true);
@ -218,7 +228,6 @@ namespace Online
// existing profile, and then delete profile. Only the returned
// pointer is save to use.
m_profile = ProfileManager::get()->addPersistent(profile);
assert(token_fetched && username_fetched && userid_fetched);
m_online_state = OS_SIGNED_IN;
if(rememberPassword())
{

Some files were not shown because too many files have changed in this diff Show More