Implement network-warnings, fixes #3264.
Shows a notification if the local client or other players connections timeout or have bad latency. This was SVN commit r17730.
This commit is contained in:
parent
4d41bd9622
commit
22f5b00fce
@ -352,6 +352,7 @@ enabledmods = "mod public"
|
||||
[overlay]
|
||||
fps = "false" ; Show frames per second in top right corner
|
||||
realtime = "false" ; Show current system time in top right corner
|
||||
netwarnings = "true" ; Show warnings if the network connection is bad
|
||||
|
||||
[profiler2]
|
||||
autoenable = false ; Enable HTTP server output at startup (default off for security/performance)
|
||||
|
@ -1,7 +1,6 @@
|
||||
/*
|
||||
DESCRIPTION : Contains global GUI functions, which will later be accessible from every GUI script/file.
|
||||
NOTES : So far, only the message box-related functions are implemented.
|
||||
*/
|
||||
/**
|
||||
* Contains global GUI functions accessible from every GUI script/file.
|
||||
*/
|
||||
|
||||
// *******************************************
|
||||
// messageBox
|
||||
@ -139,4 +138,42 @@ function updateCounters()
|
||||
var dataCounter = Engine.GetGUIObjectByName("dataCounter");
|
||||
dataCounter.caption = caption;
|
||||
dataCounter.size = sprintf("100%%-100 40 100%%-5 %(bottom)s", { bottom: 40 + 14 * linesCount });
|
||||
dataCounter.hidden = linesCount == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the overlay with the most recent network warning of each client.
|
||||
*/
|
||||
function displayGamestateNotifications()
|
||||
{
|
||||
let messages = [];
|
||||
let maxTextWidth = 0;
|
||||
|
||||
// TODO: Players who paused the game should be added here
|
||||
|
||||
// Add network warnings
|
||||
if (Engine.ConfigDB_GetValue("user", "overlay.netwarnings") == "true")
|
||||
{
|
||||
let netwarnings = getNetworkWarnings();
|
||||
messages = messages.concat(netwarnings.messages);
|
||||
maxTextWidth = Math.max(maxTextWidth, netwarnings.maxTextWidth);
|
||||
}
|
||||
|
||||
// Resize textbox
|
||||
let width = maxTextWidth + 20;
|
||||
let height = 14 * messages.length;
|
||||
|
||||
// Position left of the dataCounter
|
||||
let top = "40";
|
||||
let right = Engine.GetGUIObjectByName("dataCounter").hidden ? "100%-15" : "100%-110";
|
||||
|
||||
let bottom = top + "+" + height;
|
||||
let left = right + "-" + width;
|
||||
|
||||
let gameStateNotifications = Engine.GetGUIObjectByName("gameStateNotifications");
|
||||
gameStateNotifications.caption = messages.join("\n");
|
||||
gameStateNotifications.hidden = !messages.length;
|
||||
gameStateNotifications.size = left + " " + top + " " + right + " " + bottom;
|
||||
|
||||
setTimeout(displayGamestateNotifications, 1000);
|
||||
}
|
||||
|
@ -12,6 +12,24 @@
|
||||
|
||||
<object>
|
||||
|
||||
<!--
|
||||
==========================================
|
||||
- GAMESTATE NOTIFICATIONS
|
||||
==========================================
|
||||
-->
|
||||
<object name="gameStateNotifications"
|
||||
type="text"
|
||||
ghost="true"
|
||||
z="199"
|
||||
size="100%-110 40 100%-110 40"
|
||||
font="mono-stroke-10"
|
||||
textcolor="255 219 77"
|
||||
text_align="center"
|
||||
text_valign="top"
|
||||
sprite="color: 0 0 0 100"
|
||||
>
|
||||
</object>
|
||||
|
||||
<!--
|
||||
==========================================
|
||||
- FPS & REAL TIME & GAME TIME COUNTER
|
||||
|
@ -1,3 +1,41 @@
|
||||
/**
|
||||
* Number of milliseconds to display network warnings.
|
||||
*/
|
||||
const g_NetworkWarningTimeout = 3000;
|
||||
|
||||
/**
|
||||
* Currently displayed network warnings. At most one message per user.
|
||||
*/
|
||||
var g_NetworkWarnings = {};
|
||||
|
||||
/**
|
||||
* Message-types to be displayed.
|
||||
*/
|
||||
var g_NetworkWarningTexts = {
|
||||
|
||||
"server-timeout": (msg, username) =>
|
||||
sprintf(translate("Losing connection to server (%(seconds)s)"), {
|
||||
"seconds": Math.ceil(msg.lastReceivedTime / 1000)
|
||||
}),
|
||||
|
||||
"client-timeout": (msg, username) =>
|
||||
sprintf(translate("%(player)s losing connection (%(seconds)s)"), {
|
||||
"player": username,
|
||||
"seconds": Math.ceil(msg.lastReceivedTime / 1000)
|
||||
}),
|
||||
|
||||
"server-latency": (msg, username) =>
|
||||
sprintf(translate("Bad connection to server (%(milliseconds)sms)"), {
|
||||
"milliseconds": msg.meanRTT
|
||||
}),
|
||||
|
||||
"client-latency": (msg, username) =>
|
||||
sprintf(translate("Bad connection to %(player)s (%(milliseconds)sms)"), {
|
||||
"player": username,
|
||||
"milliseconds": msg.meanRTT
|
||||
})
|
||||
};
|
||||
|
||||
var g_NetworkCommands = {
|
||||
"/kick": argument => kickPlayer(argument, false),
|
||||
"/ban": argument => kickPlayer(argument, true),
|
||||
@ -94,3 +132,69 @@ function executeNetworkCommand(input)
|
||||
|
||||
return !!g_NetworkCommands[command];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember this warning for a few seconds.
|
||||
* Overwrite previous warnings for this user.
|
||||
*
|
||||
* @param msg - GUI message sent by NetServer or NetClient
|
||||
*/
|
||||
function addNetworkWarning(msg)
|
||||
{
|
||||
if (!g_NetworkWarningTexts[msg.warntype])
|
||||
{
|
||||
warn("Unknown network warning type received: " + uneval(msg));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Engine.ConfigDB_GetValue("user", "overlay.netwarnings") != "true")
|
||||
return;
|
||||
|
||||
g_NetworkWarnings[msg.guid || "server"] = {
|
||||
"added": Date.now(),
|
||||
"msg": msg
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorizes and concatenates all network warnings.
|
||||
* Returns text and textWidth.
|
||||
*/
|
||||
function getNetworkWarnings()
|
||||
{
|
||||
// Remove outdated messages
|
||||
for (let guid in g_NetworkWarnings)
|
||||
{
|
||||
if (Date.now() > g_NetworkWarnings[guid].added + g_NetworkWarningTimeout)
|
||||
delete g_NetworkWarnings[guid];
|
||||
|
||||
if (guid != "server" && !g_PlayerAssignments[guid])
|
||||
delete g_NetworkWarnings[guid];
|
||||
}
|
||||
|
||||
// Show local messages first
|
||||
let guids = Object.keys(g_NetworkWarnings).sort(guid => guid != "server");
|
||||
|
||||
let font = Engine.GetGUIObjectByName("gameStateNotifications").font;
|
||||
|
||||
let messages = [];
|
||||
let maxTextWidth = 0;
|
||||
|
||||
for (let guid of guids)
|
||||
{
|
||||
let msg = g_NetworkWarnings[guid].msg;
|
||||
|
||||
// Add formatted text
|
||||
messages.push(g_NetworkWarningTexts[msg.warntype](msg, colorizePlayernameByGUID(guid)));
|
||||
|
||||
// Add width of unformatted text
|
||||
let username = guid != "server" && g_PlayerAssignments[guid].name;
|
||||
let textWidth = Engine.GetTextWidth(font, g_NetworkWarningTexts[msg.warntype](msg, username));
|
||||
maxTextWidth = Math.max(textWidth, maxTextWidth);
|
||||
}
|
||||
|
||||
return {
|
||||
"messages": messages,
|
||||
"maxTextWidth": maxTextWidth
|
||||
};
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ const g_MapPath = {
|
||||
*/
|
||||
const g_NetMessageTypes = {
|
||||
"netstatus": msg => handleNetStatusMessage(msg),
|
||||
"netwarn": msg => addNetworkWarning(msg),
|
||||
"gamesetup": msg => handleGamesetupMessage(msg),
|
||||
"players": msg => handlePlayerAssignmentMessage(msg),
|
||||
"ready": msg => handleReadyMessage(msg),
|
||||
@ -211,6 +212,8 @@ function init(attribs)
|
||||
g_DefaultPlayerData.shift();
|
||||
for (let i in g_DefaultPlayerData)
|
||||
g_DefaultPlayerData[i].Civ = "random";
|
||||
|
||||
setTimeout(displayGamestateNotifications, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -946,6 +949,8 @@ function onTick()
|
||||
error("Unrecognised net message type " + message.type);
|
||||
}
|
||||
}
|
||||
|
||||
updateTimers();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,6 +8,7 @@
|
||||
<script file="gui/common/functions_utility.js"/>
|
||||
<script file="gui/common/network.js"/>
|
||||
<script file="gui/common/settings.js"/>
|
||||
<script file="gui/common/timer.js"/>
|
||||
|
||||
<!-- After settings.js, which defines g_Settings -->
|
||||
<script file="gui/gamesetup/gamesetup.js"/>
|
||||
|
@ -126,7 +126,9 @@ function pollAndHandleNetworkClient()
|
||||
break;
|
||||
|
||||
case "chat":
|
||||
// Ignore, since we have nowhere to display chat messages
|
||||
break;
|
||||
|
||||
case "netwarn":
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -169,6 +171,10 @@ function pollAndHandleNetworkClient()
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "netwarn":
|
||||
break;
|
||||
|
||||
default:
|
||||
error(sprintf("Unrecognised net message type %(messageType)s", { messageType: message.type }));
|
||||
break;
|
||||
|
@ -25,6 +25,12 @@
|
||||
"tooltip": "Show detailed tooltips for trainable units in unit-producing buildings.",
|
||||
"parameters": { "config": "showdetailedtooltips" }
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Network Warnings",
|
||||
"tooltip": "Show which player has a bad connection in multiplayer games.",
|
||||
"parameters": { "config": "overlay.netwarnings" }
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "FPS Overlay",
|
||||
|
@ -29,6 +29,7 @@ var g_ChatTimers = [];
|
||||
*/
|
||||
var g_NetMessageTypes = {
|
||||
"netstatus": msg => handleNetStatusMessage(msg),
|
||||
"netwarn": msg => addNetworkWarning(msg),
|
||||
"players": msg => handlePlayerAssignmentsMessage(msg),
|
||||
"rejoined": msg => addChatMessage({ "type": "rejoined", "guid": msg.guid }),
|
||||
"kicked": msg => addChatMessage({ "type": "system", "text": sprintf(translate("%(username)s has been kicked"), { "username": msg.username }) }),
|
||||
|
@ -269,6 +269,8 @@ function init(initData, hotloadData)
|
||||
|
||||
onSimulationUpdate();
|
||||
|
||||
setTimeout(displayGamestateNotifications, 1000);
|
||||
|
||||
// Report the performance after 5 seconds (when we're still near
|
||||
// the initial camera view) and a minute (when the profiler will
|
||||
// have settled down if framerates as very low), to give some
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
|
||||
#include "graphics/Camera.h"
|
||||
#include "graphics/FontMetrics.h"
|
||||
#include "graphics/GameView.h"
|
||||
#include "graphics/MapReader.h"
|
||||
#include "graphics/scripting/JSInterface_GameView.h"
|
||||
@ -875,6 +876,16 @@ CParamNode GetTemplate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std
|
||||
return g_GUI->GetTemplate(templateName);
|
||||
}
|
||||
|
||||
int GetTextWidth(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const CStr& fontName, const CStrW& text)
|
||||
{
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
CStrIntern _fontName(fontName);
|
||||
CFontMetrics fontMetrics(_fontName);
|
||||
fontMetrics.CalculateStringSize(text.c_str(), width, height);
|
||||
return width;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Timer
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -1049,6 +1060,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
|
||||
scriptInterface.RegisterFunction<void, std::wstring, JS::HandleValue, &WriteJSONFile>("WriteJSONFile");
|
||||
scriptInterface.RegisterFunction<bool, std::string, &TemplateExists>("TemplateExists");
|
||||
scriptInterface.RegisterFunction<CParamNode, std::string, &GetTemplate>("GetTemplate");
|
||||
scriptInterface.RegisterFunction<int, CStr, CStrW, &GetTextWidth>("GetTextWidth");
|
||||
|
||||
// User report functions
|
||||
scriptInterface.RegisterFunction<bool, &IsUserReportEnabled>("IsUserReportEnabled");
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015 Wildfire Games.
|
||||
/* Copyright (C) 2016 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -70,7 +70,8 @@ CNetClient::CNetClient(CGame* game) :
|
||||
m_Session(NULL),
|
||||
m_UserName(L"anonymous"),
|
||||
m_GUID(ps_generate_guid()), m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game),
|
||||
m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetContext())
|
||||
m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetContext()),
|
||||
m_LastConnectionCheck(0)
|
||||
{
|
||||
m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it
|
||||
|
||||
@ -94,12 +95,16 @@ CNetClient::CNetClient(CGame* game) :
|
||||
AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
|
||||
AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
|
||||
AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context);
|
||||
AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_PREGAME, (void*)&OnClientTimeout, context);
|
||||
AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_PREGAME, (void*)&OnClientPerformance, context);
|
||||
AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
|
||||
AddTransition(NCS_PREGAME, (uint)NMT_JOIN_SYNC_START, NCS_JOIN_SYNCING, (void*)&OnJoinSyncStart, context);
|
||||
|
||||
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CHAT, NCS_JOIN_SYNCING, (void*)&OnChat, context);
|
||||
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_SETUP, NCS_JOIN_SYNCING, (void*)&OnGameSetup, context);
|
||||
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_JOIN_SYNCING, (void*)&OnPlayerAssignment, context);
|
||||
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_TIMEOUT, NCS_JOIN_SYNCING, (void*)&OnClientTimeout, context);
|
||||
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_PERFORMANCE, NCS_JOIN_SYNCING, (void*)&OnClientPerformance, context);
|
||||
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_START, NCS_JOIN_SYNCING, (void*)&OnGameStart, context);
|
||||
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_SIMULATION_COMMAND, NCS_JOIN_SYNCING, (void*)&OnInGame, context);
|
||||
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_END_COMMAND_BATCH, NCS_JOIN_SYNCING, (void*)&OnJoinSyncEndCommandBatch, context);
|
||||
@ -108,10 +113,14 @@ CNetClient::CNetClient(CGame* game) :
|
||||
AddTransition(NCS_LOADING, (uint)NMT_CHAT, NCS_LOADING, (void*)&OnChat, context);
|
||||
AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
|
||||
AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
|
||||
AddTransition(NCS_LOADING, (uint)NMT_CLIENT_TIMEOUT, NCS_LOADING, (void*)&OnClientTimeout, context);
|
||||
AddTransition(NCS_LOADING, (uint)NMT_CLIENT_PERFORMANCE, NCS_LOADING, (void*)&OnClientPerformance, context);
|
||||
AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
|
||||
|
||||
AddTransition(NCS_INGAME, (uint)NMT_REJOINED, NCS_INGAME, (void*)&OnRejoined, context);
|
||||
AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context);
|
||||
AddTransition(NCS_INGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_INGAME, (void*)&OnClientTimeout, context);
|
||||
AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_INGAME, (void*)&OnClientPerformance, context);
|
||||
AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
|
||||
AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
|
||||
AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
|
||||
@ -170,8 +179,45 @@ void CNetClient::DestroyConnection()
|
||||
|
||||
void CNetClient::Poll()
|
||||
{
|
||||
if (m_Session)
|
||||
m_Session->Poll();
|
||||
if (!m_Session)
|
||||
return;
|
||||
|
||||
CheckServerConnection();
|
||||
m_Session->Poll();
|
||||
}
|
||||
|
||||
void CNetClient::CheckServerConnection()
|
||||
{
|
||||
// Trigger local warnings if the connection to the server is bad.
|
||||
// At most once per second.
|
||||
std::time_t now = std::time(nullptr);
|
||||
if (now <= m_LastConnectionCheck)
|
||||
return;
|
||||
|
||||
m_LastConnectionCheck = now;
|
||||
|
||||
JSContext* cx = GetScriptInterface().GetContext();
|
||||
|
||||
// Report if we are losing the connection to the server
|
||||
u32 lastReceived = m_Session->GetLastReceivedTime();
|
||||
if (lastReceived > NETWORK_WARNING_TIMEOUT)
|
||||
{
|
||||
JS::RootedValue msg(cx);
|
||||
GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-timeout' })", &msg);
|
||||
GetScriptInterface().SetProperty(msg, "lastReceivedTime", lastReceived);
|
||||
PushGuiMessage(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Report if we have a bad ping to the server
|
||||
u32 meanRTT = m_Session->GetMeanRTT();
|
||||
if (meanRTT > DEFAULT_TURN_LENGTH_MP)
|
||||
{
|
||||
JS::RootedValue msg(cx);
|
||||
GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-latency' })", &msg);
|
||||
GetScriptInterface().SetProperty(msg, "meanRTT", meanRTT);
|
||||
PushGuiMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void CNetClient::Flush()
|
||||
@ -627,6 +673,60 @@ bool CNetClient::OnKicked(void *context, CFsmEvent* event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetClient::OnClientTimeout(void *context, CFsmEvent* event)
|
||||
{
|
||||
// Report the timeout of some other client
|
||||
|
||||
ENSURE(event->GetType() == (uint)NMT_CLIENT_TIMEOUT);
|
||||
|
||||
CNetClient* client = (CNetClient*)context;
|
||||
JSContext* cx = client->GetScriptInterface().GetContext();
|
||||
|
||||
if (client->GetCurrState() == NCS_LOADING)
|
||||
return true;
|
||||
|
||||
CClientTimeoutMessage* message = (CClientTimeoutMessage*)event->GetParamRef();
|
||||
JS::RootedValue msg(cx);
|
||||
|
||||
client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-timeout' })", &msg);
|
||||
client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID));
|
||||
client->GetScriptInterface().SetProperty(msg, "lastReceivedTime", message->m_LastReceivedTime);
|
||||
client->PushGuiMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetClient::OnClientPerformance(void *context, CFsmEvent* event)
|
||||
{
|
||||
// Performance statistics for one or multiple clients
|
||||
|
||||
ENSURE(event->GetType() == (uint)NMT_CLIENT_PERFORMANCE);
|
||||
|
||||
CNetClient* client = (CNetClient*)context;
|
||||
JSContext* cx = client->GetScriptInterface().GetContext();
|
||||
|
||||
if (client->GetCurrState() == NCS_LOADING)
|
||||
return true;
|
||||
|
||||
CClientPerformanceMessage* message = (CClientPerformanceMessage*)event->GetParamRef();
|
||||
std::vector<CClientPerformanceMessage::S_m_Clients> &clients = message->m_Clients;
|
||||
|
||||
// Display warnings for other clients with bad ping
|
||||
for (size_t i = 0; i < clients.size(); ++i)
|
||||
{
|
||||
if (clients[i].m_MeanRTT < DEFAULT_TURN_LENGTH_MP || clients[i].m_GUID == client->m_GUID)
|
||||
continue;
|
||||
|
||||
JS::RootedValue msg(cx);
|
||||
client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-latency' })", &msg);
|
||||
client->GetScriptInterface().SetProperty(msg, "guid", clients[i].m_GUID);
|
||||
client->GetScriptInterface().SetProperty(msg, "meanRTT", clients[i].m_MeanRTT);
|
||||
client->PushGuiMessage(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
|
||||
{
|
||||
ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015 Wildfire Games.
|
||||
/* Copyright (C) 2016 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -108,6 +108,11 @@ public:
|
||||
*/
|
||||
void Poll();
|
||||
|
||||
/**
|
||||
* Locally triggers a GUI message if the connection to the server is being lost or has bad latency.
|
||||
*/
|
||||
void CheckServerConnection();
|
||||
|
||||
/**
|
||||
* Flush any queued outgoing network messages.
|
||||
* This should be called soon after sending a group of messages that may be batched together.
|
||||
@ -202,6 +207,8 @@ private:
|
||||
static bool OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event);
|
||||
static bool OnRejoined(void* context, CFsmEvent* event);
|
||||
static bool OnKicked(void* context, CFsmEvent* event);
|
||||
static bool OnClientTimeout(void* context, CFsmEvent* event);
|
||||
static bool OnClientPerformance(void* context, CFsmEvent* event);
|
||||
static bool OnLoadedGame(void* context, CFsmEvent* event);
|
||||
|
||||
/**
|
||||
@ -240,6 +247,9 @@ private:
|
||||
|
||||
/// Serialized game state received when joining an in-progress game
|
||||
std::string m_JoinSyncBuffer;
|
||||
|
||||
/// Time when the server was last checked for timeouts and bad latency
|
||||
std::time_t m_LastConnectionCheck;
|
||||
};
|
||||
|
||||
/// Global network client for the standard game
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015 Wildfire Games.
|
||||
/* Copyright (C) 2016 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -139,6 +139,14 @@ CNetMessage* CNetMessageFactory::CreateMessage(const void* pData,
|
||||
pNewMessage = new CKickedMessage;
|
||||
break;
|
||||
|
||||
case NMT_CLIENT_TIMEOUT:
|
||||
pNewMessage = new CClientTimeoutMessage;
|
||||
break;
|
||||
|
||||
case NMT_CLIENT_PERFORMANCE:
|
||||
pNewMessage = new CClientPerformanceMessage;
|
||||
break;
|
||||
|
||||
case NMT_LOADED_GAME:
|
||||
pNewMessage = new CLoadedGameMessage;
|
||||
break;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015 Wildfire Games.
|
||||
/* Copyright (C) 2016 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -28,7 +28,7 @@
|
||||
|
||||
#define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?'
|
||||
#define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!'
|
||||
#define PS_PROTOCOL_VERSION 0x01010008 // Arbitrary protocol
|
||||
#define PS_PROTOCOL_VERSION 0x01010009 // Arbitrary protocol
|
||||
#define PS_DEFAULT_PORT 0x5073 // 'P', 's'
|
||||
|
||||
// Defines the list of message types. The order of the list must not change.
|
||||
@ -60,6 +60,9 @@ enum NetMessageType
|
||||
NMT_REJOINED,
|
||||
NMT_KICKED,
|
||||
|
||||
NMT_CLIENT_TIMEOUT,
|
||||
NMT_CLIENT_PERFORMANCE,
|
||||
|
||||
NMT_LOADED_GAME,
|
||||
NMT_GAME_START,
|
||||
NMT_END_COMMAND_BATCH,
|
||||
@ -167,6 +170,18 @@ START_NMT_CLASS_(Kicked, NMT_KICKED)
|
||||
NMT_FIELD_INT(m_Ban, u8, 1)
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(ClientTimeout, NMT_CLIENT_TIMEOUT)
|
||||
NMT_FIELD(CStr8, m_GUID)
|
||||
NMT_FIELD_INT(m_LastReceivedTime, u32, 4)
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(ClientPerformance, NMT_CLIENT_PERFORMANCE)
|
||||
NMT_START_ARRAY(m_Clients)
|
||||
NMT_FIELD(CStr8, m_GUID)
|
||||
NMT_FIELD_INT(m_MeanRTT, u32, 4)
|
||||
NMT_END_ARRAY()
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(LoadedGame, NMT_LOADED_GAME)
|
||||
NMT_FIELD_INT(m_CurrentTurn, u32, 4)
|
||||
END_NMT_CLASS()
|
||||
|
@ -121,7 +121,8 @@ CNetServerWorker::CNetServerWorker(int autostartPlayers) :
|
||||
m_AutostartPlayers(autostartPlayers),
|
||||
m_Shutdown(false),
|
||||
m_ScriptInterface(NULL),
|
||||
m_NextHostID(1), m_Host(NULL), m_HostGUID(), m_Stats(NULL)
|
||||
m_NextHostID(1), m_Host(NULL), m_HostGUID(), m_Stats(NULL),
|
||||
m_LastConnectionCheck(0)
|
||||
{
|
||||
m_State = SERVER_STATE_UNCONNECTED;
|
||||
|
||||
@ -451,6 +452,8 @@ bool CNetServerWorker::RunStep()
|
||||
for (size_t i = 0; i < m_Sessions.size(); ++i)
|
||||
m_Sessions[i]->GetFileTransferer().Poll();
|
||||
|
||||
CheckClientConnections();
|
||||
|
||||
// Process network events:
|
||||
|
||||
ENetEvent event;
|
||||
@ -549,6 +552,55 @@ bool CNetServerWorker::RunStep()
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNetServerWorker::CheckClientConnections()
|
||||
{
|
||||
if (m_State == SERVER_STATE_LOADING)
|
||||
return;
|
||||
|
||||
// Send messages at most once per second
|
||||
std::time_t now = std::time(nullptr);
|
||||
if (now <= m_LastConnectionCheck)
|
||||
return;
|
||||
|
||||
m_LastConnectionCheck = now;
|
||||
|
||||
for (size_t i = 0; i < m_Sessions.size(); ++i)
|
||||
{
|
||||
u32 lastReceived = m_Sessions[i]->GetLastReceivedTime();
|
||||
u32 meanRTT = m_Sessions[i]->GetMeanRTT();
|
||||
|
||||
CNetMessage* message = nullptr;
|
||||
|
||||
// Report if we didn't hear from the client since few seconds
|
||||
if (lastReceived > NETWORK_WARNING_TIMEOUT)
|
||||
{
|
||||
CClientTimeoutMessage* msg = new CClientTimeoutMessage();
|
||||
msg->m_GUID = m_Sessions[i]->GetGUID();
|
||||
msg->m_LastReceivedTime = lastReceived;
|
||||
message = msg;
|
||||
}
|
||||
// Report if the client has bad ping
|
||||
else if (meanRTT > DEFAULT_TURN_LENGTH_MP)
|
||||
{
|
||||
CClientPerformanceMessage* msg = new CClientPerformanceMessage();
|
||||
CClientPerformanceMessage::S_m_Clients client;
|
||||
client.m_GUID = m_Sessions[i]->GetGUID();
|
||||
client.m_MeanRTT = meanRTT;
|
||||
msg->m_Clients.push_back(client);
|
||||
message = msg;
|
||||
}
|
||||
|
||||
// Send to all clients except the affected one
|
||||
// (since that will show the locally triggered warning instead)
|
||||
if (message)
|
||||
for (size_t j = 0; j < m_Sessions.size(); ++j)
|
||||
if (i != j)
|
||||
m_Sessions[j]->SendMessage(message);
|
||||
|
||||
SAFE_DELETE(message);
|
||||
}
|
||||
}
|
||||
|
||||
void CNetServerWorker::HandleMessageReceive(const CNetMessage* message, CNetServerSession* session)
|
||||
{
|
||||
// Handle non-FSM messages first
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015 Wildfire Games.
|
||||
/* Copyright (C) 2016 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -281,6 +281,10 @@ private:
|
||||
|
||||
void HandleMessageReceive(const CNetMessage* message, CNetServerSession* session);
|
||||
|
||||
/**
|
||||
* Send a network warning if the connection to a client is being lost or has bad latency.
|
||||
*/
|
||||
void CheckClientConnections();
|
||||
|
||||
/**
|
||||
* Internal script context for (de)serializing script messages,
|
||||
@ -331,6 +335,11 @@ private:
|
||||
*/
|
||||
std::string m_JoinSyncFile;
|
||||
|
||||
/**
|
||||
* Time when the clients connections were last checked for timeouts and latency.
|
||||
*/
|
||||
std::time_t m_LastConnectionCheck;
|
||||
|
||||
private:
|
||||
// Thread-related stuff:
|
||||
|
||||
|
@ -25,6 +25,8 @@
|
||||
#include "ps/CLogger.h"
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
|
||||
const u32 NETWORK_WARNING_TIMEOUT = 4000;
|
||||
|
||||
static const int CHANNEL_COUNT = 1;
|
||||
|
||||
CNetClientSession::CNetClientSession(CNetClient& client) :
|
||||
@ -168,6 +170,22 @@ bool CNetClientSession::SendMessage(const CNetMessage* message)
|
||||
return CNetHost::SendMessage(message, m_Server, "server");
|
||||
}
|
||||
|
||||
u32 CNetClientSession::GetLastReceivedTime() const
|
||||
{
|
||||
if (!m_Server)
|
||||
return 0;
|
||||
|
||||
return enet_time_get() - m_Server->lastReceiveTime;
|
||||
}
|
||||
|
||||
u32 CNetClientSession::GetMeanRTT() const
|
||||
{
|
||||
if (!m_Server)
|
||||
return 0;
|
||||
|
||||
return m_Server->roundTripTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
CNetServerSession::CNetServerSession(CNetServerWorker& server, ENetPeer* peer) :
|
||||
@ -184,6 +202,22 @@ CStr CNetServerSession::GetIPAddress() const
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
u32 CNetServerSession::GetLastReceivedTime() const
|
||||
{
|
||||
if (!m_Peer)
|
||||
return 0;
|
||||
|
||||
return enet_time_get() - m_Peer->lastReceiveTime;
|
||||
}
|
||||
|
||||
u32 CNetServerSession::GetMeanRTT() const
|
||||
{
|
||||
if (!m_Peer)
|
||||
return 0;
|
||||
|
||||
return m_Peer->roundTripTime;
|
||||
}
|
||||
|
||||
void CNetServerSession::Disconnect(u32 reason)
|
||||
{
|
||||
Update((uint)NMT_CONNECTION_LOST, NULL);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015 Wildfire Games.
|
||||
/* Copyright (C) 2016 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -24,6 +24,11 @@
|
||||
#include "ps/CStr.h"
|
||||
#include "scriptinterface/ScriptVal.h"
|
||||
|
||||
/**
|
||||
* Report the peer if we didn't receive a packet after this time (milliseconds).
|
||||
*/
|
||||
extern const u32 NETWORK_WARNING_TIMEOUT;
|
||||
|
||||
class CNetClient;
|
||||
class CNetServerWorker;
|
||||
|
||||
@ -83,6 +88,16 @@ public:
|
||||
*/
|
||||
virtual bool SendMessage(const CNetMessage* message);
|
||||
|
||||
/**
|
||||
* Number of milliseconds since the most recent packet of the server was received.
|
||||
*/
|
||||
u32 GetLastReceivedTime() const;
|
||||
|
||||
/**
|
||||
* Average round trip time to the server.
|
||||
*/
|
||||
u32 GetMeanRTT() const;
|
||||
|
||||
CNetFileTransferer& GetFileTransferer() { return m_FileTransferer; }
|
||||
|
||||
private:
|
||||
@ -124,6 +139,16 @@ public:
|
||||
|
||||
CStr GetIPAddress() const;
|
||||
|
||||
/**
|
||||
* Number of milliseconds since the latest packet of that client was received.
|
||||
*/
|
||||
u32 GetLastReceivedTime() const;
|
||||
|
||||
/**
|
||||
* Average round trip time to the client.
|
||||
*/
|
||||
u32 GetMeanRTT() const;
|
||||
|
||||
/**
|
||||
* Sends a disconnection notification to the client,
|
||||
* and sends a NMT_CONNECTION_LOST message to the session FSM.
|
||||
|
@ -38,8 +38,8 @@
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
|
||||
static const int DEFAULT_TURN_LENGTH_MP = 500;
|
||||
static const int DEFAULT_TURN_LENGTH_SP = 200;
|
||||
const u32 DEFAULT_TURN_LENGTH_MP = 500;
|
||||
const u32 DEFAULT_TURN_LENGTH_SP = 200;
|
||||
|
||||
static const int COMMAND_DELAY = 2;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015 Wildfire Games.
|
||||
/* Copyright (C) 2016 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -26,6 +26,9 @@
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
extern const u32 DEFAULT_TURN_LENGTH_MP;
|
||||
extern const u32 DEFAULT_TURN_LENGTH_SP;
|
||||
|
||||
class CNetServerWorker;
|
||||
class CNetClient;
|
||||
class CSimulationMessage;
|
||||
|
Loading…
Reference in New Issue
Block a user