Ticket #3264: t3264_show_network_warnings_v1.patch

File t3264_show_network_warnings_v1.patch, 34.0 KB (added by elexis, 8 years ago)
  • binaries/data/config/default.cfg

    xpartamupp = "wfgbot20" ; Na  
    350350enabledmods = "mod public"
    351351
    352352[overlay]
    353353fps = "false"                     ; Show frames per second in top right corner
    354354realtime = "false"                ; Show current system time in top right corner
     355netwarnings = "true"              ; Show warnings if the network connection is bad
    355356
    356357[profiler2]
    357358autoenable = false                ; Enable HTTP server output at startup (default off for security/performance)
    358359script.enable = false             ; Enable Javascript profiling. Needs to be set before startup and can't be changed later. (default off for performance)
    359360gpu.arb.enable = true             ; Allow GL_ARB_timer_query timing mode when available
  • binaries/data/mods/public/gui/common/functions_global_object.js

     
    1 /*
    2     DESCRIPTION : Contains global GUI functions, which will later be accessible from every GUI script/file.
    3     NOTES       : So far, only the message box-related functions are implemented.
    4 */
     1/**
     2 * Contains global GUI functions accessible from every GUI script/file.
     3 * File will be reloaded when switching pages or showing message boxes!
     4 */
     5
     6/**
     7 * Number of milliseconds to display network warnings.
     8 */
     9var g_NetworkWarningTimeout = 3000;
     10
     11/**
     12 * Currently displayed network warnings. At most one message per user.
     13 */
     14var g_NetworkWarnings = {};
     15
     16/**
     17 * Update GUI object once per second.
     18 */
     19var g_NetworkWarningsLastUpdate = 0;
     20
     21/**
     22 * Message-types to be displayed.
     23 */
     24var g_NetworkWarningTexts = {
     25
     26    "server-timeout": (msg, username) =>
     27        sprintf(translate("Losing connection to server (%(seconds)s)"), {
     28            "seconds": Math.ceil(msg.lastReceivedTime / 1000)
     29        }),
     30
     31    "client-timeout": (msg, username) =>
     32        sprintf(translate("%(player)s losing connection (%(seconds)s)"), {
     33            "player": username,
     34            "seconds": Math.ceil(msg.lastReceivedTime / 1000)
     35        }),
     36
     37    "server-latency": (msg, username) =>
     38        sprintf(translate("Bad connection to server (%(milliseconds)sms)"), {
     39            "milliseconds": msg.meanRTT
     40        }),
     41
     42    "client-latency": (msg, username) =>
     43        sprintf(translate("Bad connection to %(player)s (%(milliseconds)sms)"), {
     44            "player": username,
     45            "milliseconds": msg.meanRTT
     46        })
     47};
    548
    649// *******************************************
    750// messageBox
    851// *******************************************
    952// @params:     int mbWidth, int mbHeight, string mbMessage, string mbTitle, int mbMode, arr mbButtonCaptions, function mbBtnCode, var mbCallbackArgs
    function updateCounters()  
    138181
    139182    var dataCounter = Engine.GetGUIObjectByName("dataCounter");
    140183    dataCounter.caption = caption;
    141184    dataCounter.size = sprintf("100%%-100 40 100%%-5 %(bottom)s", { bottom: 40 + 14 * linesCount });
    142185}
     186
     187/**
     188 * @param msg - GUI message sent by NetServer or NetClient
     189 */
     190function addNetworkWarning(msg)
     191{
     192    if (Engine.ConfigDB_GetValue("user", "overlay.netwarnings") != "true")
     193        return;
     194
     195    if (!g_NetworkWarningTexts[msg.warntype])
     196    {
     197        warn("Unknown network warning type received: " + JSON.stringify(msg));
     198        return;
     199    }
     200
     201    // Remember this message for few seconds.
     202    // Overwrite previous messages for this user.
     203    g_NetworkWarnings[msg.guid || "server"] = {
     204        "added": Date.now(),
     205        "msg": msg
     206    };
     207}
     208
     209/**
     210 * Displays the most recent network warning of each client onscreen.
     211 */
     212function displayNetworkWarnings()
     213{
     214    // Update once per second
     215    let now = Date.now();
     216    if (now <= g_NetworkWarningsLastUpdate)
     217        return;
     218
     219    // Hide GUI object if disabled
     220    let showWarnings = Engine.ConfigDB_GetValue("user", "overlay.netwarnings") == "true";
     221
     222    let networkWarnings = Engine.GetGUIObjectByName("networkWarnings");
     223    networkWarnings.hidden = !showWarnings;
     224
     225    if (!showWarnings)
     226        return;
     227
     228    // Remove outdated messages
     229    for (let guid in g_NetworkWarnings)
     230        if (now - g_NetworkWarnings[guid].added > g_NetworkWarningTimeout || !g_PlayerAssignments[guid])
     231            delete g_NetworkWarnings[guid];
     232
     233    // Sort messages to show local ones first
     234    let guids = Object.keys(g_NetworkWarnings).sort(guid => guid != "server");
     235
     236    // Get text and width
     237    let formattedMessages = [];
     238    let maxTextWidth = 0;
     239    for (let guid of guids)
     240    {
     241        let msg = g_NetworkWarnings[guid].msg;
     242
     243        // Colorize message
     244        formattedMessages.push(g_NetworkWarningTexts[msg.warntype](msg, colorizePlayernameByGUID(guid)));
     245
     246        // Get width of unformatted text
     247        let textWidth = Engine.GetTextWidth("mono-stroke-10", g_NetworkWarningTexts[msg.warntype](msg, g_PlayerAssignments[guid].name));
     248        maxTextWidth = Math.max(textWidth, maxTextWidth);
     249    }
     250
     251    // Update textbox size
     252    let width = maxTextWidth + 20;
     253    let height = 14 * formattedMessages.length;
     254
     255    let top = "40";
     256    let right = "100%-110";
     257    let bottom = top + "+" + height;
     258    let left = right + "-" + width;
     259
     260    networkWarnings.caption = formattedMessages.join("\n");
     261    networkWarnings.size = left + " " + top + " " + right + " " + bottom;
     262}
  • binaries/data/mods/public/gui/common/global.xml

     
    1212
    1313    <object>
    1414
    1515        <!--
    1616        ==========================================
     17        - NETWORK WARNINGS
     18        ==========================================
     19        -->
     20        <object name="networkWarnings"
     21            type="text"
     22            ghost="true"
     23            z="199"
     24            size="100%-110 40 100%-110 40"
     25            font="mono-stroke-10"
     26            textcolor="255 219 77"
     27            text_align="center"
     28            text_valign="top"
     29            sprite="color: 0 0 0 100"
     30            >
     31            <action on="Tick">
     32                displayNetworkWarnings();
     33            </action>
     34        </object>
     35
     36        <!--
     37        ==========================================
    1738        - FPS & REAL TIME & GAME TIME COUNTER
    1839        ==========================================
    1940        -->
    2041        <object name="dataCounter"
    2142            type="text"
  • binaries/data/mods/public/gui/gamesetup/gamesetup.js

    const g_MapPath = {  
    2626/**
    2727 * Processes a CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer.
    2828 */
    2929const g_NetMessageTypes = {
    3030    "netstatus": msg => handleNetStatusMessage(msg),
     31    "netwarn": msg => addNetworkWarning(msg),
    3132    "gamesetup": msg => handleGamesetupMessage(msg),
    3233    "players": msg => handlePlayerAssignmentMessage(msg),
    3334    "ready": msg => handleReadyMessage(msg),
    3435    "start": msg => handleGamestartMessage(msg),
    3536    "kicked": msg => addChatMessage({ "type": "kicked", "username": msg.username }),
  • binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js

    function pollAndHandleNetworkClient()  
    124124                    "playerAssignments": g_PlayerAssignments
    125125                });
    126126                break;
    127127
    128128            case "chat":
    129                 // Ignore, since we have nowhere to display chat messages
     129                break;
     130
     131            case "netwarn":
    130132                break;
    131133
    132134            default:
    133135                error(sprintf("Unrecognised net message type %(messageType)s", { messageType: message.type }));
    134136            }
    function pollAndHandleNetworkClient()  
    167169                default:
    168170                    error(sprintf("Unrecognised netstatus type %(netType)s", { netType: message.status }));
    169171                    break;
    170172                }
    171173                break;
     174
     175            case "netwarn":
     176                break;
     177
    172178            default:
    173179                error(sprintf("Unrecognised net message type %(messageType)s", { messageType: message.type }));
    174180                break;
    175181            }
    176182        }
  • binaries/data/mods/public/gui/options/options.json

     
    2525            "tooltip": "Show detailed tooltips for trainable units in unit-producing buildings.",
    2626            "parameters": { "config": "showdetailedtooltips" }
    2727        },
    2828        {
    2929            "type": "boolean",
     30            "label": "Network Warnings",
     31            "tooltip": "Show which player has a bad connection in multiplayer games.",
     32            "parameters": { "config": "overlay.netwarnings" }
     33        },
     34        {
     35            "type": "boolean",
    3036            "label": "FPS Overlay",
    3137            "tooltip": "Show frames per second in top right corner.",
    3238            "parameters": { "config": "overlay.fps" }
    3339        },
    3440        {
  • binaries/data/mods/public/gui/session/messages.js

    var g_ChatTimers = [];  
    2727/**
    2828 * Handle all netmessage types that can occur.
    2929 */
    3030var g_NetMessageTypes = {
    3131    "netstatus": msg => handleNetStatusMessage(msg),
     32    "netwarn": msg => addNetworkWarning(msg),
    3233    "players": msg => handlePlayerAssignmentsMessage(msg),
    3334    "rejoined": msg => addChatMessage({ "type": "rejoined", "guid": msg.guid }),
    3435    "kicked": msg => addChatMessage({ "type": "system", "text": sprintf(translate("%(username)s has been kicked"), { "username": msg.username }) }),
    3536    "banned": msg => addChatMessage({ "type": "system", "text": sprintf(translate("%(username)s has been banned"), { "username": msg.username }) }),
    3637    "chat": msg => addChatMessage({ "type": "message", "guid": msg.guid, "text": msg.text }),
  • source/gui/scripting/ScriptFunctions.cpp

     
    1818#include "precompiled.h"
    1919
    2020#include "scriptinterface/ScriptInterface.h"
    2121
    2222#include "graphics/Camera.h"
     23#include "graphics/FontMetrics.h"
    2324#include "graphics/GameView.h"
    2425#include "graphics/MapReader.h"
    2526#include "graphics/scripting/JSInterface_GameView.h"
    2627#include "gui/GUI.h"
    2728#include "gui/GUIManager.h"
    bool TemplateExists(ScriptInterface::CxP  
    873874CParamNode GetTemplate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& templateName)
    874875{
    875876    return g_GUI->GetTemplate(templateName);
    876877}
    877878
     879int GetTextWidth(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& fontName, const std::wstring& text)
     880{
     881    int width = 0;
     882    int height = 0;
     883    CStrIntern _fontName (fontName);
     884    CFontMetrics fontMetrics(_fontName);
     885    fontMetrics.CalculateStringSize(text.c_str(), width, height);
     886    return width;
     887}
     888
    878889//-----------------------------------------------------------------------------
    879890// Timer
    880891//-----------------------------------------------------------------------------
    881892
    882893
    void GuiScriptingInit(ScriptInterface& s  
    10471058    scriptInterface.RegisterFunction<std::wstring, int, &GetBuildTimestamp>("GetBuildTimestamp");
    10481059    scriptInterface.RegisterFunction<JS::Value, std::wstring, &ReadJSONFile>("ReadJSONFile");
    10491060    scriptInterface.RegisterFunction<void, std::wstring, JS::HandleValue, &WriteJSONFile>("WriteJSONFile");
    10501061    scriptInterface.RegisterFunction<bool, std::string, &TemplateExists>("TemplateExists");
    10511062    scriptInterface.RegisterFunction<CParamNode, std::string, &GetTemplate>("GetTemplate");
     1063    scriptInterface.RegisterFunction<int, std::string, std::wstring, &GetTextWidth>("GetTextWidth");
    10521064
    10531065    // User report functions
    10541066    scriptInterface.RegisterFunction<bool, &IsUserReportEnabled>("IsUserReportEnabled");
    10551067    scriptInterface.RegisterFunction<void, bool, &SetUserReportEnabled>("SetUserReportEnabled");
    10561068    scriptInterface.RegisterFunction<std::string, &GetUserReportStatus>("GetUserReportStatus");
  • source/network/NetClient.cpp

     
    1 /* Copyright (C) 2015 Wildfire Games.
     1/* Copyright (C) 2016 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
    private:  
    6868
    6969CNetClient::CNetClient(CGame* game) :
    7070    m_Session(NULL),
    7171    m_UserName(L"anonymous"),
    7272    m_GUID(ps_generate_guid()), m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game),
    73     m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetContext())
     73    m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetContext()),
     74    m_LastConnectionCheck(0)
    7475{
    7576    m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it
    7677
    7778    void* context = this;
    7879
    CNetClient::CNetClient(CGame* game) :  
    9293    AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context);
    9394    AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context);
    9495    AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
    9596    AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
    9697    AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context);
     98    AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_PREGAME, (void*)&OnClientTimeout, context);
     99    AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_PREGAME, (void*)&OnClientPerformance, context);
    97100    AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
    98101    AddTransition(NCS_PREGAME, (uint)NMT_JOIN_SYNC_START, NCS_JOIN_SYNCING, (void*)&OnJoinSyncStart, context);
    99102
    100103    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CHAT, NCS_JOIN_SYNCING, (void*)&OnChat, context);
    101104    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_SETUP, NCS_JOIN_SYNCING, (void*)&OnGameSetup, context);
    102105    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_JOIN_SYNCING, (void*)&OnPlayerAssignment, context);
     106    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_TIMEOUT, NCS_JOIN_SYNCING, (void*)&OnClientTimeout, context);
     107    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_PERFORMANCE, NCS_JOIN_SYNCING, (void*)&OnClientPerformance, context);
    103108    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_START, NCS_JOIN_SYNCING, (void*)&OnGameStart, context);
    104109    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_SIMULATION_COMMAND, NCS_JOIN_SYNCING, (void*)&OnInGame, context);
    105110    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_END_COMMAND_BATCH, NCS_JOIN_SYNCING, (void*)&OnJoinSyncEndCommandBatch, context);
    106111    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
    107112
    108113    AddTransition(NCS_LOADING, (uint)NMT_CHAT, NCS_LOADING, (void*)&OnChat, context);
    109114    AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
    110115    AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
     116    AddTransition(NCS_LOADING, (uint)NMT_CLIENT_TIMEOUT, NCS_LOADING, (void*)&OnClientTimeout, context);
     117    AddTransition(NCS_LOADING, (uint)NMT_CLIENT_PERFORMANCE, NCS_LOADING, (void*)&OnClientPerformance, context);
    111118    AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
    112119
    113120    AddTransition(NCS_INGAME, (uint)NMT_REJOINED, NCS_INGAME, (void*)&OnRejoined, context);
    114121    AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context);
     122    AddTransition(NCS_INGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_INGAME, (void*)&OnClientTimeout, context);
     123    AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_INGAME, (void*)&OnClientPerformance, context);
    115124    AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
    116125    AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
    117126    AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
    118127    AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
    119128    AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context);
    void CNetClient::DestroyConnection()  
    168177    SAFE_DELETE(m_Session);
    169178}
    170179
    171180void CNetClient::Poll()
    172181{
    173     if (m_Session)
    174         m_Session->Poll();
     182    if (!m_Session)
     183        return;
     184
     185    CheckServerConnection();
     186    m_Session->Poll();
     187}
     188
     189void CNetClient::CheckServerConnection()
     190{
     191    // Trigger local warnings if the connection to the server is bad.
     192    // At most once per second.
     193    std::time_t now = std::time(nullptr);
     194    if (now <= m_LastConnectionCheck)
     195        return;
     196
     197    m_LastConnectionCheck = now;
     198
     199    JSContext* cx = GetScriptInterface().GetContext();
     200
     201    // Report if we are losing the connection to the server
     202    u32 lastReceived = m_Session->GetLastReceivedTime();
     203    if (lastReceived > NETWORK_WARNING_TIMEOUT)
     204    {
     205        JS::RootedValue msg(cx);
     206        GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-timeout' })", &msg);
     207        GetScriptInterface().SetProperty(msg, "lastReceivedTime", lastReceived);
     208        PushGuiMessage(msg);
     209        return;
     210    }
     211
     212    // Report if we have a bad ping to the server
     213    u32 meanRTT = m_Session->GetMeanRTT();
     214    if (meanRTT > DEFAULT_TURN_LENGTH_MP * NETWORK_WARNING_LATENCY)
     215    {
     216        JS::RootedValue msg(cx);
     217        GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-latency' })", &msg);
     218        GetScriptInterface().SetProperty(msg, "meanRTT", meanRTT);
     219        PushGuiMessage(msg);
     220    }
    175221}
    176222
    177223void CNetClient::Flush()
    178224{
    179225    if (m_Session)
    bool CNetClient::OnKicked(void *context,  
    625671    client->PushGuiMessage(msg);
    626672
    627673    return true;
    628674}
    629675
     676bool CNetClient::OnClientTimeout(void *context, CFsmEvent* event)
     677{
     678    // Report the timeout of some other client
     679
     680    ENSURE(event->GetType() == (uint)NMT_CLIENT_TIMEOUT);
     681
     682    CNetClient* client = (CNetClient*)context;
     683    JSContext* cx = client->GetScriptInterface().GetContext();
     684
     685    if (client->GetCurrState() == NCS_LOADING)
     686        return true;
     687
     688    CClientTimeoutMessage* message = (CClientTimeoutMessage*)event->GetParamRef();
     689    JS::RootedValue msg(cx);
     690
     691    client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-timeout' })", &msg);
     692    client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID));
     693    client->GetScriptInterface().SetProperty(msg, "lastReceivedTime", message->m_LastReceivedTime);
     694    client->PushGuiMessage(msg);
     695
     696    return true;
     697}
     698
     699bool CNetClient::OnClientPerformance(void *context, CFsmEvent* event)
     700{
     701    // Performance statistics for one or multiple clients
     702
     703    ENSURE(event->GetType() == (uint)NMT_CLIENT_PERFORMANCE);
     704
     705    CNetClient* client = (CNetClient*)context;
     706    JSContext* cx = client->GetScriptInterface().GetContext();
     707
     708    if (client->GetCurrState() == NCS_LOADING)
     709        return true;
     710
     711    CClientPerformanceMessage* message = (CClientPerformanceMessage*)event->GetParamRef();
     712    std::vector<CClientPerformanceMessage::S_m_Clients> &clients = message->m_Clients;
     713
     714    // Display warnings for other clients with bad ping
     715    for (size_t i = 0; i < clients.size(); ++i)
     716    {
     717        u32 meanRTT = clients[i].m_MeanRTT;
     718
     719        if (meanRTT < DEFAULT_TURN_LENGTH_MP * NETWORK_WARNING_LATENCY || clients[i].m_GUID == client->m_GUID)
     720            continue;
     721
     722        JS::RootedValue msg(cx);
     723        client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-latency' })", &msg);
     724        client->GetScriptInterface().SetProperty(msg, "guid", clients[i].m_GUID);
     725        client->GetScriptInterface().SetProperty(msg, "meanRTT", meanRTT);
     726        client->PushGuiMessage(msg);
     727    }
     728
     729    return true;
     730}
     731
    630732bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
    631733{
    632734    ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
    633735
    634736    CNetClient* client = (CNetClient*)context;
  • source/network/NetClient.h

     
    1 /* Copyright (C) 2015 Wildfire Games.
     1/* Copyright (C) 2016 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
    public:  
    107107     * This must be called frequently (i.e. once per frame).
    108108     */
    109109    void Poll();
    110110
    111111    /**
     112     * Locally triggers a GUI message if the connection to the server is being lost or has bad latency.
     113     */
     114    void CheckServerConnection();
     115
     116    /**
    112117     * Flush any queued outgoing network messages.
    113118     * This should be called soon after sending a group of messages that may be batched together.
    114119     */
    115120    void Flush();
    116121
    private:  
    200205    static bool OnGameStart(void* context, CFsmEvent* event);
    201206    static bool OnJoinSyncStart(void* context, CFsmEvent* event);
    202207    static bool OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event);
    203208    static bool OnRejoined(void* context, CFsmEvent* event);
    204209    static bool OnKicked(void* context, CFsmEvent* event);
     210    static bool OnClientTimeout(void* context, CFsmEvent* event);
     211    static bool OnClientPerformance(void* context, CFsmEvent* event);
    205212    static bool OnLoadedGame(void* context, CFsmEvent* event);
    206213
    207214    /**
    208215     * Take ownership of a session object, and use it for all network communication.
    209216     */
    private:  
    238245    /// Queue of messages for GuiPoll
    239246    std::deque<JS::Heap<JS::Value> > m_GuiMessageQueue;
    240247
    241248    /// Serialized game state received when joining an in-progress game
    242249    std::string m_JoinSyncBuffer;
     250
     251    /// Time when the server was last checked for timeout
     252    std::time_t m_LastConnectionCheck;
    243253};
    244254
    245255/// Global network client for the standard game
    246256extern CNetClient *g_NetClient;
    247257
  • source/network/NetMessage.cpp

     
    1 /* Copyright (C) 2015 Wildfire Games.
     1/* Copyright (C) 2016 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
    CNetMessage* CNetMessageFactory::CreateM  
    137137
    138138    case NMT_KICKED:
    139139        pNewMessage = new CKickedMessage;
    140140        break;
    141141
     142    case NMT_CLIENT_TIMEOUT:
     143        pNewMessage = new CClientTimeoutMessage;
     144        break;
     145
     146    case NMT_CLIENT_PERFORMANCE:
     147        pNewMessage = new CClientPerformanceMessage;
     148        break;
     149
    142150    case NMT_LOADED_GAME:
    143151        pNewMessage = new CLoadedGameMessage;
    144152        break;
    145153
    146154    case NMT_SERVER_HANDSHAKE:
  • source/network/NetMessages.h

     
    1 /* Copyright (C) 2015 Wildfire Games.
     1/* Copyright (C) 2016 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    2626#include "ps/CStr.h"
    2727#include "scriptinterface/ScriptVal.h"
    2828
    2929#define PS_PROTOCOL_MAGIC               0x5073013f      // 'P', 's', 0x01, '?'
    3030#define PS_PROTOCOL_MAGIC_RESPONSE      0x50630121      // 'P', 'c', 0x01, '!'
    31 #define PS_PROTOCOL_VERSION             0x01010008      // Arbitrary protocol
     31#define PS_PROTOCOL_VERSION             0x01010009      // Arbitrary protocol
    3232#define PS_DEFAULT_PORT                 0x5073          // 'P', 's'
    3333
    3434// Defines the list of message types. The order of the list must not change.
    3535// The message types having a negative value are used internally and not sent
    3636// over the network. The message types used for network communication have
    enum NetMessageType  
    5858    NMT_JOIN_SYNC_START,
    5959
    6060    NMT_REJOINED,
    6161    NMT_KICKED,
    6262
     63    NMT_CLIENT_TIMEOUT,
     64    NMT_CLIENT_PERFORMANCE,
     65
    6366    NMT_LOADED_GAME,
    6467    NMT_GAME_START,
    6568    NMT_END_COMMAND_BATCH,
    6669    NMT_SYNC_CHECK, // OOS-detection hash checking
    6770    NMT_SYNC_ERROR, // OOS-detection error
    END_NMT_CLASS()  
    165168START_NMT_CLASS_(Kicked, NMT_KICKED)
    166169    NMT_FIELD(CStrW, m_Name)
    167170    NMT_FIELD_INT(m_Ban, u8, 1)
    168171END_NMT_CLASS()
    169172
     173START_NMT_CLASS_(ClientTimeout, NMT_CLIENT_TIMEOUT)
     174    NMT_FIELD(CStr8, m_GUID)
     175    NMT_FIELD_INT(m_LastReceivedTime, u32, 4)
     176END_NMT_CLASS()
     177
     178START_NMT_CLASS_(ClientPerformance, NMT_CLIENT_PERFORMANCE)
     179    NMT_START_ARRAY(m_Clients)
     180        NMT_FIELD(CStr8, m_GUID)
     181        NMT_FIELD_INT(m_MeanRTT, u32, 4)
     182    NMT_END_ARRAY()
     183END_NMT_CLASS()
     184
    170185START_NMT_CLASS_(LoadedGame, NMT_LOADED_GAME)
    171186    NMT_FIELD_INT(m_CurrentTurn, u32, 4)
    172187END_NMT_CLASS()
    173188
    174189START_NMT_CLASS_(GameStart, NMT_GAME_START)
  • source/network/NetServer.cpp

    private:  
    119119
    120120CNetServerWorker::CNetServerWorker(int autostartPlayers) :
    121121    m_AutostartPlayers(autostartPlayers),
    122122    m_Shutdown(false),
    123123    m_ScriptInterface(NULL),
    124     m_NextHostID(1), m_Host(NULL), m_HostGUID(), m_Stats(NULL)
     124    m_NextHostID(1), m_Host(NULL), m_HostGUID(), m_Stats(NULL),
     125    m_LastConnectionCheck(0)
    125126{
    126127    m_State = SERVER_STATE_UNCONNECTED;
    127128
    128129    m_ServerTurnManager = NULL;
    129130
    bool CNetServerWorker::RunStep()  
    449450
    450451    // Perform file transfers
    451452    for (size_t i = 0; i < m_Sessions.size(); ++i)
    452453        m_Sessions[i]->GetFileTransferer().Poll();
    453454
     455    CheckClientConnections();
     456
    454457    // Process network events:
    455458
    456459    ENetEvent event;
    457460    int status = enet_host_service(m_Host, &event, HOST_SERVICE_TIMEOUT);
    458461    if (status < 0)
    bool CNetServerWorker::RunStep()  
    547550    }
    548551
    549552    return true;
    550553}
    551554
     555void CNetServerWorker::CheckClientConnections()
     556{
     557    if (m_State == SERVER_STATE_LOADING)
     558        return;
     559
     560    // Send messages at most once per second
     561    std::time_t now = std::time(nullptr);
     562    if (now <= m_LastConnectionCheck)
     563        return;
     564
     565    m_LastConnectionCheck = now;
     566
     567    for (size_t i = 0; i < m_Sessions.size(); ++i)
     568    {
     569        u32 lastReceived = m_Sessions[i]->GetLastReceivedTime();
     570        u32 meanRTT = m_Sessions[i]->GetMeanRTT();
     571
     572        CNetMessage* message = nullptr;
     573
     574        // Report if we didn't hear from the client since few seconds
     575        if (lastReceived > NETWORK_WARNING_TIMEOUT)
     576        {
     577            CClientTimeoutMessage* msg = new CClientTimeoutMessage();
     578            msg->m_GUID = m_Sessions[i]->GetGUID();
     579            msg->m_LastReceivedTime = lastReceived;
     580            message = msg;
     581        }
     582        // Report if the client has bad ping
     583        else if (meanRTT > DEFAULT_TURN_LENGTH_MP * NETWORK_WARNING_LATENCY)
     584        {
     585            CClientPerformanceMessage* msg = new CClientPerformanceMessage();
     586            CClientPerformanceMessage::S_m_Clients client;
     587            client.m_GUID = m_Sessions[i]->GetGUID();
     588            client.m_MeanRTT = meanRTT;
     589            msg->m_Clients.push_back(client);
     590            message = msg;
     591        }
     592
     593        // Send to all clients except the affected one
     594        // (since that will show the locally triggered warning instead)
     595        if (message)
     596            for (size_t j = 0; j < m_Sessions.size(); ++j)
     597                if (i != j)
     598                    m_Sessions[j]->SendMessage(message);
     599
     600        SAFE_DELETE(message);
     601    }
     602}
     603
    552604void CNetServerWorker::HandleMessageReceive(const CNetMessage* message, CNetServerSession* session)
    553605{
    554606    // Handle non-FSM messages first
    555607    Status status = session->GetFileTransferer().HandleMessageReceive(message);
    556608    if (status != INFO::SKIPPED)
  • source/network/NetServer.h

     
    1 /* Copyright (C) 2015 Wildfire Games.
     1/* Copyright (C) 2016 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
    private:  
    279279
    280280    void ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage& message);
    281281
    282282    void HandleMessageReceive(const CNetMessage* message, CNetServerSession* session);
    283283
     284    /**
     285     * Send a network warning if the connection to a client is being lost or has bad latency.
     286     */
     287    void CheckClientConnections();
    284288
    285289    /**
    286290     * Internal script context for (de)serializing script messages,
    287291     * and for storing game attributes.
    288292     * (TODO: we shouldn't bother deserializing (except for debug printing of messages),
    private:  
    329333     * The latest copy of the simulation state, received from an existing
    330334     * client when a new client has asked to rejoin the game.
    331335     */
    332336    std::string m_JoinSyncFile;
    333337
     338    /**
     339     *  Time when the clients connections were last checked for timeouts and latency.
     340     */
     341    std::time_t m_LastConnectionCheck;
     342
    334343private:
    335344    // Thread-related stuff:
    336345
    337346#if CONFIG2_MINIUPNPC
    338347    /**
  • source/network/NetSession.cpp

     
    2323#include "NetStats.h"
    2424#include "lib/external_libraries/enet.h"
    2525#include "ps/CLogger.h"
    2626#include "scriptinterface/ScriptInterface.h"
    2727
     28const u32 NETWORK_WARNING_TIMEOUT = 4000;
     29const float NETWORK_WARNING_LATENCY = 1.1f;
     30
    2831static const int CHANNEL_COUNT = 1;
    2932
    3033CNetClientSession::CNetClientSession(CNetClient& client) :
    3134    m_Client(client), m_FileTransferer(this), m_Host(NULL), m_Server(NULL), m_Stats(NULL)
    3235{
    bool CNetClientSession::SendMessage(cons  
    166169    ENSURE(m_Host && m_Server);
    167170
    168171    return CNetHost::SendMessage(message, m_Server, "server");
    169172}
    170173
     174u32 CNetClientSession::GetLastReceivedTime() const
     175{
     176    if (!m_Server)
     177        return 0;
     178
     179    return enet_time_get() - m_Server->lastReceiveTime;
     180}
     181
     182u32 CNetClientSession::GetMeanRTT() const
     183{
     184    if (!m_Server)
     185        return 0;
     186
     187    return m_Server->roundTripTime;
     188}
     189
    171190
    172191
    173192CNetServerSession::CNetServerSession(CNetServerWorker& server, ENetPeer* peer) :
    174193    m_Server(server), m_FileTransferer(this), m_Peer(peer)
    175194{
    CStr CNetServerSession::GetIPAddress() c  
    182201        LOGMESSAGE("Could not get IP address of a client!");
    183202
    184203    return ipAddress;
    185204}
    186205
     206u32 CNetServerSession::GetLastReceivedTime() const
     207{
     208    if (!m_Peer)
     209        return 0;
     210
     211    return enet_time_get() - m_Peer->lastReceiveTime;
     212}
     213
     214u32 CNetServerSession::GetMeanRTT() const
     215{
     216    if (!m_Peer)
     217        return 0;
     218
     219    return m_Peer->roundTripTime;
     220}
     221
    187222void CNetServerSession::Disconnect(u32 reason)
    188223{
    189224    Update((uint)NMT_CONNECTION_LOST, NULL);
    190225
    191226    enet_peer_disconnect(m_Peer, reason);
  • source/network/NetSession.h

     
    1 /* Copyright (C) 2015 Wildfire Games.
     1/* Copyright (C) 2016 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    2222#include "network/NetFileTransfer.h"
    2323#include "network/NetHost.h"
    2424#include "ps/CStr.h"
    2525#include "scriptinterface/ScriptVal.h"
    2626
     27/**
     28 * Report the peer if we didn't receive a packet after this time (milliseconds).
     29 */
     30extern const u32 NETWORK_WARNING_TIMEOUT;
     31
     32/**
     33 * Report the peer if the mean RTT takes longer than this multiple of a turnlength.
     34 */
     35extern const float NETWORK_WARNING_LATENCY;
     36
    2737class CNetClient;
    2838class CNetServerWorker;
    2939
    3040class CNetStatsTable;
    3141
    public:  
    8191    /**
    8292     * Send a message to the server.
    8393     */
    8494    virtual bool SendMessage(const CNetMessage* message);
    8595
     96    /**
     97     * Number of milliseconds since the most recent packet of the server was received.
     98     */
     99    u32 GetLastReceivedTime() const;
     100
     101    /**
     102     * Average round trip time to the server.
     103     */
     104    u32 GetMeanRTT() const;
     105
    86106    CNetFileTransferer& GetFileTransferer() { return m_FileTransferer; }
    87107
    88108private:
    89109    CNetClient& m_Client;
    90110
    public:  
    123143    void SetHostID(u32 id) { m_HostID = id; }
    124144
    125145    CStr GetIPAddress() const;
    126146
    127147    /**
     148     * Number of milliseconds since the latest packet of that client was received.
     149     */
     150    u32 GetLastReceivedTime() const;
     151
     152    /**
     153     * Average round trip time to the client.
     154     */
     155    u32 GetMeanRTT() const;
     156
     157    /**
    128158     * Sends a disconnection notification to the client,
    129159     * and sends a NMT_CONNECTION_LOST message to the session FSM.
    130160     * The server will receive a disconnection notification after a while.
    131161     * The server will not receive any further messages sent via this session.
    132162     */
  • source/network/NetTurnManager.cpp

     
    3636
    3737#include <sstream>
    3838#include <fstream>
    3939#include <iomanip>
    4040
    41 static const int DEFAULT_TURN_LENGTH_MP = 500;
    42 static const int DEFAULT_TURN_LENGTH_SP = 200;
     41const int DEFAULT_TURN_LENGTH_MP = 500;
     42const int DEFAULT_TURN_LENGTH_SP = 200;
    4343
    4444static const int COMMAND_DELAY = 2;
    4545
    4646#if 0
    4747#define NETTURN_LOG(args) debug_printf args
  • source/network/NetTurnManager.h

     
    1 /* Copyright (C) 2015 Wildfire Games.
     1/* Copyright (C) 2016 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    2424
    2525#include <list>
    2626#include <map>
    2727#include <vector>
    2828
     29extern const int DEFAULT_TURN_LENGTH_MP;
     30extern const int DEFAULT_TURN_LENGTH_SP;
     31
    2932class CNetServerWorker;
    3033class CNetClient;
    3134class CSimulationMessage;
    3235class CSimulation2;
    3336class IReplayLogger;