Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions code/_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ unsigned char EventClass::EventLength[EventClass::LAST_EVENT] = {
0, // PAGEUSER
size_of(EventClass, Data.General), // REMOVEPLAYER
size_of(EventClass, Data.General), // LATENCYFUDGE
size_of(EventClass, Data.NetworkReport), // NETWORK_REPORT
};

char const * EventClass::EventNames[EventClass::LAST_EVENT] = {
Expand Down Expand Up @@ -96,4 +97,5 @@ char const * EventClass::EventNames[EventClass::LAST_EVENT] = {
"PAGEUSER",
"REMOVEPLAYER",
"LATENCYFUDGE",
"NETWORK_REPORT",
};
11 changes: 11 additions & 0 deletions code/connect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,17 @@ unsigned int ConnectionClass::Time (void)
} /* end of Time */


/// <summary>Reports this link's last measured round trip.</summary>
/// <returns>Returns the smoothed round trip, or nothing until a clean acknowledgement has been measured.</returns>
std::optional<NetTiming::Milliseconds> ConnectionClass::Smoothed_Round_Trip_MS(void) const
{
if (!RoundTripEstimator.Has_Sample() || RoundTripEstimator.Is_Provisional()) {
return(std::nullopt);
}
return(RoundTripEstimator.Smoothed_Rtt());
}


/***************************************************************************
* ConnectionClass::Command_Name -- returns name for given packet command *
* *
Expand Down
3 changes: 3 additions & 0 deletions code/connect.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
#include "netadmit.h"
#include "nettiming.h"

#include <optional>

/*
********************************** Defines **********************************
*/
Expand Down Expand Up @@ -186,6 +188,7 @@ class ConnectionClass
void Set_TimeOut (unsigned int t) { Timeout = t;}
unsigned int Max_Packet_Len (void) { return(MaxPacketLen); }
void Reset_Round_Trip_Time(void) {RoundTripEstimator.Reset();}
std::optional<NetTiming::Milliseconds> Smoothed_Round_Trip_MS(void) const;
static const char * Command_Name(int command);

int Num_Resends(void) const { return(NumResends); }
Expand Down
5 changes: 5 additions & 0 deletions code/connmgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#pragma once

#include "nettime.h"

#include <optional>


/*
***************************** Class Declaration *****************************
Expand Down Expand Up @@ -120,6 +124,7 @@ class ConnManClass
.....................................................................*/
virtual void Reset_Response_Time(bool zero) = 0;
virtual unsigned int Response_Time(void) = 0;
virtual std::optional<NetTiming::Milliseconds> Worst_Local_Round_Trip_MS(void) const = 0;
virtual void Set_Timing (unsigned int retrydelta,
unsigned int maxretries, unsigned int timeout, bool set_external = true) = 0;
virtual void Set_External_Timing (unsigned int retrydelta,
Expand Down
87 changes: 66 additions & 21 deletions code/event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
#include "ramp.hh"
#include "special.hh"

#include <cstdint>
#include <limits>


namespace {
enum class EventRejectReason : unsigned int {
Expand All @@ -93,7 +96,10 @@ namespace {
InvalidLatencyFudge,
UnauthorizedSubject,
UnauthorizedTiming,
InvalidTimingArithmetic,
InvalidTimingValues,
UnschedulableTiming,
InvalidNetworkReport,
Count,
};

Expand All @@ -112,7 +118,10 @@ namespace {
"invalid latency fudge",
"unauthorized subject",
"unauthorized timing",
"invalid timing arithmetic",
"invalid timing values",
"unschedulable timing",
"invalid network report",
};

static_assert(ARRAY_SIZE(EventRejectReasonNames) == (int)EventRejectReason::Count);
Expand Down Expand Up @@ -707,7 +716,6 @@ void EventClass::Execute(void)
// bool formation = false;
int i;
int index;
unsigned int ul;
// RTTIType rt;

//if (Debug_Print_Events) {
Expand Down Expand Up @@ -1209,7 +1217,7 @@ void EventClass::Execute(void)
Log_Event_Rejection(EventRejectReason::InvalidTimingValues, Type, ID, Data.FrameInfo.Delay);
break;
}
Session.MaxAhead = Data.FrameInfo.Delay;
Session.Apply_Network_Response_Time(Data.FrameInfo.Delay, Frame >= 0 ? static_cast<unsigned int>(Frame) : 0u);
break;
}

Expand Down Expand Up @@ -1250,6 +1258,7 @@ void EventClass::Execute(void)

DebugString("Executing REMOVEPLAYER event. Frame is %d\n", ::Frame);
Disable_Multiplayer_Saving();
Session.Remove_Network_Timing_Player(index, Frame >= 0 ? static_cast<unsigned int>(Frame) : 0u);
house = Houses[index];
if (house->IsObserver) {
break;
Expand Down Expand Up @@ -1295,17 +1304,48 @@ void EventClass::Execute(void)
break;
}

unsigned int const padding = Scen->Special.IsFogOfWar ? 10 : 0;
if (Data.Timing.MaxAhead < padding) {
if (Session.CommProtocol != COMM_PROTOCOL_MULTI_E_COMP || Frame < 0) {
Log_Event_Rejection(EventRejectReason::InvalidTimingArithmetic, Type, ID, Frame);
break;
}
if (!NetSemantic::Timing_Values_Are_Valid(Data.Timing.DesiredFrameRate, Data.Timing.MaxAhead, Data.Timing.FrameSendRate)) {
Log_Event_Rejection(EventRejectReason::InvalidTimingValues, Type, ID, Data.Timing.MaxAhead);
break;
}
unsigned int const max_ahead = Data.Timing.MaxAhead - padding;
if (!NetSemantic::Timing_Values_Are_Valid(Data.Timing.DesiredFrameRate, max_ahead, Data.Timing.FrameSendRate)) {
Log_Event_Rejection(EventRejectReason::InvalidTimingValues, Type, ID, max_ahead);

NetTiming::TimingSettings const settings{Data.Timing.FrameSendRate, Data.Timing.MaxAhead};
NetTiming::ConnectionQuality const old_quality = NetTiming::Connection_Quality_For_Settings(Session.Network_Timing_Target());
unsigned int const old_frame_send_rate = Session.FrameSendRate;
unsigned int const old_max_ahead = Session.MaxAhead;

if (settings.MaxAhead > old_max_ahead || settings.FrameSendRate > old_frame_send_rate) {
std::uint64_t const boundary = settings.FrameSendRate * ((static_cast<std::uint64_t>(Frame) + NetTiming::MAXIMUM_MAX_AHEAD
+ settings.FrameSendRate - 1) / settings.FrameSendRate);
if (boundary > static_cast<std::uint64_t>((std::numeric_limits<int>::max)())) {
Log_Event_Rejection(EventRejectReason::InvalidTimingArithmetic, Type, ID, Frame);
break;
}
}

NetTiming::ScheduleResult const result = Session.Schedule_Network_Timing(settings, Data.Timing.DesiredFrameRate, static_cast<unsigned int>(Frame));
if (result == NetTiming::ScheduleResult::Rejected) {
Log_Event_Rejection(EventRejectReason::UnschedulableTiming, Type, ID, static_cast<int>(settings.MaxAhead));
break;
}

DebugString("Network timing event at frame %d from player %d: %u/%u at %u fps %s\n", Frame, ID, settings.FrameSendRate, settings.MaxAhead,
Data.Timing.DesiredFrameRate, result == NetTiming::ScheduleResult::Applied ? "applied" : "staged");
NetTiming::ConnectionQuality const quality = NetTiming::Connection_Quality_For_Settings(settings);
if (quality != old_quality) {
char const * format = Fetch_String(TXT_CONNECTION_QUALITY_STATUS);
char const * quality_name = Fetch_String(Network_Quality_Text_ID(quality));
if (format != NULL && quality_name != NULL && format[0] != '\0' && quality_name[0] != '\0') {
snprintf(msg, sizeof(msg), format, quality_name);
Session.Messages.Add_Message(NULL, 0, msg, house->Scheme,
TextPrintType(TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW), Rule->MessageDelay * TICKS_PER_MINUTE);
}
}

#if (TIMING_FIX)
//
// If MaxAhead is about to increase, we're vulnerable to a Packet-
Expand All @@ -1314,26 +1354,16 @@ void EventClass::Execute(void)
// period of vulnerability's frame start & end values, so we
// can reschedule these events to execute after it's over.
//
if (max_ahead > Session.MaxAhead || Data.Timing.FrameSendRate > Session.FrameSendRate) {
if (result == NetTiming::ScheduleResult::Applied && (Session.MaxAhead > old_max_ahead || Session.FrameSendRate > old_frame_send_rate)) {
std::uint64_t const boundary = Session.FrameSendRate * ((static_cast<std::uint64_t>(Frame) + Session.MaxAhead
+ Session.FrameSendRate - 1) / Session.FrameSendRate);
NewMaxAheadFrame1 = Frame;
NewMaxAheadFrame2 = Data.Timing.FrameSendRate * ((Data.Timing.FrameSendRate + max_ahead + Frame - 1) / Data.Timing.FrameSendRate);
NewMaxAheadFrame2 = static_cast<int>(boundary);
} else {
NewMaxAheadFrame1 = 0;
NewMaxAheadFrame2 = 0;
}
#endif

ul = Session.MaxMaxAhead;

Session.DesiredFrameRate = Data.Timing.DesiredFrameRate;
Session.MaxAhead = max_ahead;

if (ul <= Session.MaxAhead) {
Session.MaxMaxAhead = Session.MaxAhead;
}

Session.FrameSendRate = Data.Timing.FrameSendRate;

break;
}

Expand All @@ -1351,6 +1381,21 @@ void EventClass::Execute(void)
}
break;

case NETWORK_REPORT:
// A recording started without a roster has nobody to attribute reports to.
if ((Session.CommProtocol != COMM_PROTOCOL_MULTI_E_COMP || Frame < 0
|| !Session.Record_Network_Report(ID, Data.NetworkReport.AverageProcessMilliseconds,
Data.NetworkReport.WorstRoundTripMilliseconds, Data.NetworkReport.StallMilliseconds, static_cast<unsigned int>(Frame)))
&& !Session.Play) {
Log_Event_Rejection(EventRejectReason::InvalidNetworkReport, Type, ID, Data.NetworkReport.WorstRoundTripMilliseconds);
} else if (!Session.Play) {
DebugString("Network report at frame %d from player %d: process %u ms, RTT %d ms, longest wait %u ms\n", Frame, ID,
(unsigned int)Data.NetworkReport.AverageProcessMilliseconds,
Data.NetworkReport.WorstRoundTripMilliseconds == NETWORK_RTT_UNAVAILABLE ? -1 : (int)Data.NetworkReport.WorstRoundTripMilliseconds,
(unsigned int)Data.NetworkReport.StallMilliseconds);
}
break;

/*
** Default: do nothing.
*/
Expand Down
10 changes: 10 additions & 0 deletions code/event.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "mph.hh"
#include "speed.hh"

#include <cstdint>
#include <cstring>

/*
Expand Down Expand Up @@ -108,10 +109,13 @@ class EventClass
REMOVEPLAYER,

LATENCYFUDGE,
NETWORK_REPORT,

LAST_EVENT, // one past the last event
};

static constexpr std::uint16_t NETWORK_RTT_UNAVAILABLE = UINT16_MAX;

unsigned char Type; // Type of queue command object.

/*
Expand Down Expand Up @@ -236,6 +240,12 @@ class EventClass
unsigned short AverageTicks;
} ProcessTime;

struct {
std::uint16_t AverageProcessMilliseconds;
std::uint16_t WorstRoundTripMilliseconds;
std::uint16_t StallMilliseconds;
} NetworkReport;

} Data;

//-------------- Constructors ---------------------
Expand Down
64 changes: 33 additions & 31 deletions code/goptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +115,30 @@ void Game_Options_Dialog(void)
}


/// <summary>Returns the localized label for a synchronized connection-quality tier.</summary>
int Network_Quality_Text_ID(NetTiming::ConnectionQuality quality)
{
switch (quality) {
case NetTiming::ConnectionQuality::Fast: return(TXT_BEST_CONNECTION);
case NetTiming::ConnectionQuality::Normal: return(TXT_GOOD_CONNECTION);
case NetTiming::ConnectionQuality::Poor: return(TXT_POOR_CONNECTION);
case NetTiming::ConnectionQuality::Bad: return(TXT_WORST_CONNECTION);
}
return(TXT_WORST_CONNECTION);
}


/// <summary>
/// Handles messages for the in game options dialog.
/// This routine offers every message to the owner draw system first. What is left it uses
/// to service the option buttons -- save, load, delete, briefing, resume, abort and
/// settings -- either acting on them directly or noting the player's choice for
/// Game_Options_Dialog to deal with once the dialog comes down. Dragging the game speed or
/// connection quality slider updates the label beside it.
/// Game_Options_Dialog to deal with once the dialog comes down. Dragging the game speed
/// slider updates the label beside it.
/// </summary>
/// <returns>Returns with TRUE if the owner draw system consumed the message.</returns>
BOOL CALLBACK Game_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
{
static int GameConnectionQualityNames[] = {
TXT_WORST_CONNECTION,
TXT_POOR_CONNECTION,
TXT_GOOD_CONNECTION,
TXT_BEST_CONNECTION
};

BOOL rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam);

HWND handle;
Expand Down Expand Up @@ -204,14 +210,6 @@ BOOL CALLBACK Game_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam,
case IDC_RESUME_MISSION:
if (!code) {
if (Session.Type == GAME_INTERNET) {
handle = GetDlgItem(window, IDC_CTRLWOL_CONNECTION);
if (handle) {
int fudge = 3 - SendMessage(handle, TBM_GETPOS, 0, 0);
if (fudge != Session.LatencyFudge) {
OutList.push_back(EventClass(PlayerPtr->HeapID, EventClass::LATENCYFUDGE, fudge));
DebugString("LATENCYFUDGE event created - %d\n", fudge);
}
}
handle = GetDlgItem(window, IDC_GAME_SPEED_SLIDER);
if (handle) {
int speed = (OptionsClass::MAX_SPEED_SETTING-1) - SendMessage(handle, TBM_GETPOS, 0, 0);
Expand Down Expand Up @@ -254,20 +252,11 @@ BOOL CALLBACK Game_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam,
case WM_HSCROLL: {
if (LOWORD(wparam) == SB_THUMBTRACK) {
int pos = HIWORD(wparam);
int textid;

if ((HWND)lparam == GetDlgItem(window, IDC_GAME_SPEED_SLIDER)) {
textid = GameSpeedNames[pos];
handle = GetDlgItem(window, IDC_GAME_SPEED_LABEL);
} else if ((HWND)lparam == GetDlgItem(window, IDC_CTRLWOL_CONNECTION)) {
textid = GameConnectionQualityNames[pos];
handle = GetDlgItem(window, IDC_SCROLL_SPEED_LABEL);
} else {
break;
}

if (handle) {
Static_SetText(handle, Fetch_String(textid));
if (handle) {
Static_SetText(handle, Fetch_String(GameSpeedNames[pos]));
}
}
}
break;
Expand All @@ -285,7 +274,7 @@ BOOL CALLBACK Game_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam,
/// Prepares the controls of the game options dialog.
/// This routine is called when the dialog is created, and again whenever a save or delete
/// has changed what is on disk. It decides which buttons the current game type allows the
/// player to use and primes the game speed and connection quality sliders.
/// player to use and primes the game speed and connection-quality controls.
/// </summary>
void Game_Options_On_INITDIALOG(HWND window)
{
Expand Down Expand Up @@ -313,10 +302,23 @@ void Game_Options_On_INITDIALOG(HWND window)
}

if (Session.Type == GAME_INTERNET) {
NetTiming::TimingSettings const settings{Session.FrameSendRate, Session.MaxAhead};
NetTiming::ConnectionQuality const quality = NetTiming::Connection_Quality_For_Settings(settings);

handle = GetDlgItem(window, IDC_CTRLWOL_CONNECTION);
if (handle) {
SetSliderRangeAndPos(handle, 0, 3, 3 - Session.LatencyFudge);
unsigned int const displayed_rung = settings.FrameSendRate >= NetTiming::MINIMUM_TIMING_RUNG
&& settings.FrameSendRate <= NetTiming::MAXIMUM_TIMING_RUNG ? settings.FrameSendRate : NetTiming::MAXIMUM_TIMING_RUNG;
int const mirrored_rung = NetTiming::MINIMUM_TIMING_RUNG + NetTiming::MAXIMUM_TIMING_RUNG - displayed_rung;
SetSliderRangeAndPos(handle, NetTiming::MINIMUM_TIMING_RUNG, NetTiming::MAXIMUM_TIMING_RUNG, mirrored_rung);
EnableWindow(handle, FALSE);
}

handle = GetDlgItem(window, IDC_SCROLL_SPEED_LABEL);
if (handle) {
char label[64];
snprintf(label, sizeof(label), Fetch_String(TXT_CONNECTION_QUALITY_RUNG), Fetch_String(Network_Quality_Text_ID(quality)), settings.FrameSendRate);
Static_SetText(handle, label);
}

handle = GetDlgItem(window, IDC_GAME_SPEED_SLIDER);
Expand Down
2 changes: 2 additions & 0 deletions code/goptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#pragma once

#include "gadget.h"
#include "nettiming.h"
#include "options.h"


Expand All @@ -42,4 +43,5 @@ class GameOptionsClass : public OptionsClass {
};

int Abort_Dialog(void);
int Network_Quality_Text_ID(NetTiming::ConnectionQuality quality);
void Game_Options_Dialog(void);
Loading