Merge remote-tracking branch 'origin/network_improvements'
This commit is contained in:
commit
43cf29af86
@ -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" >
|
||||
|
@ -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>
|
||||
|
@ -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
42
doc/physics_order
Normal 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.
|
@ -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);
|
||||
}
|
||||
|
@ -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/*")
|
||||
|
@ -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"))
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
{
|
||||
|
@ -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
|
||||
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
// -------------
|
||||
|
@ -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;
|
||||
|
@ -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) &&
|
||||
|
@ -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
|
||||
|
270
src/main.cpp
270
src/main.cpp
@ -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", "=====================");
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -318,8 +318,6 @@ public:
|
||||
* quadgraph. Override to change value. */
|
||||
virtual bool useChecklineRequirements() const { return false; }
|
||||
// ------------------------------------------------------------------------
|
||||
void delayedSelfDestruct();
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void escapePressed();
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void loadCustomModels() {}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -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 {}
|
||||
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
327
src/network/protocols/game_protocol.cpp
Normal file
327
src/network/protocols/game_protocol.cpp
Normal 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
|
107
src/network/protocols/game_protocol.hpp
Normal file
107
src/network/protocols/game_protocol.hpp
Normal 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
|
@ -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
|
||||
|
@ -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; }
|
||||
|
@ -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
|
@ -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
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
@ -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
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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
|
@ -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
|
||||
|
||||
|
@ -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. */
|
||||
|
@ -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;
|
||||
|
@ -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
422
src/network/rewind_manager.cpp
Normal file → Executable 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
|
||||
// ----------------------------------------------------------------------------
|
@ -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
513
src/network/rewind_queue.cpp
Executable 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
118
src/network/rewind_queue.hpp
Executable file
@ -0,0 +1,118 @@
|
||||
//
|
||||
// SuperTuxKart - a fun racing game with go-kart
|
||||
// Copyright (C) 2017 Joerg Henrichs
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 3
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
#ifndef HEADER_REWIND_QUEUE_HPP
|
||||
#define HEADER_REWIND_QUEUE_HPP
|
||||
|
||||
#include "network/rewinder.hpp"
|
||||
#include "utils/ptr_vector.hpp"
|
||||
#include "utils/synchronised.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
class 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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
|
101
src/network/time_step_info.cpp
Normal file
101
src/network/time_step_info.cpp
Normal 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
|
||||
|
102
src/network/time_step_info.hpp
Normal file
102
src/network/time_step_info.hpp
Normal 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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user