Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
135 changes: 120 additions & 15 deletions src/connectdlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ CConnectDlg::CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteR
bServerListItemWasChosen ( false ),
bListFilterWasActive ( false ),
bShowAllMusicians ( true ),
bEnableIPv6 ( bNEnableIPv6 )
bEnableIPv6 ( bNEnableIPv6 ),
iKeepPingAfterHideStartTimestamp ( 0 )
{
setupUi ( this );

Expand Down Expand Up @@ -162,6 +163,8 @@ CConnectDlg::CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteR
// setup timers
TimerInitialSort.setSingleShot ( true ); // only once after list request

TimerKeepPingAfterHide.setSingleShot ( true );

#if defined( ANDROID ) || defined( Q_OS_IOS )
// for the Android and iOS version maximize the window
setWindowState ( Qt::WindowMaximized );
Expand Down Expand Up @@ -197,10 +200,15 @@ CConnectDlg::CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteR
QObject::connect ( &TimerPing, &QTimer::timeout, this, &CConnectDlg::OnTimerPing );

QObject::connect ( &TimerReRequestServList, &QTimer::timeout, this, &CConnectDlg::OnTimerReRequestServList );

QObject::connect ( &TimerKeepPingAfterHide, &QTimer::timeout, this, &CConnectDlg::OnTimerKeepPingAfterHide );
}

void CConnectDlg::showEvent ( QShowEvent* )
{
// Stop shutdown timer if dialog is shown again before it expires
TimerKeepPingAfterHide.stop();

// load stored IP addresses in combo box
cbxServerAddr->clear();
cbxServerAddr->clearEditText();
Expand All @@ -220,6 +228,9 @@ void CConnectDlg::showEvent ( QShowEvent* )

void CConnectDlg::RequestServerList()
{
// Ensure shutdown timer is stopped when requesting new server list
TimerKeepPingAfterHide.stop();

// reset flags
bServerListReceived = false;
bReducedServerListReceived = false;
Expand Down Expand Up @@ -271,9 +282,15 @@ void CConnectDlg::RequestServerList()

void CConnectDlg::hideEvent ( QHideEvent* )
{
// if window is closed, stop timers
TimerPing.stop();
// Stop the regular server list request timer immediately
TimerReRequestServList.stop();

iKeepPingAfterHideStartTimestamp = QDateTime::currentMSecsSinceEpoch();

// this will initiate the "after hide" phase where ping will continue, at the end of which pinging will stop
const int iRandomizedShutdownMs =
static_cast<int> ( KEEP_PING_RUNNING_AFTER_HIDE_MS * ( 0.8f + QRandomGenerator::global()->generateDouble() * 0.4f ) );
TimerKeepPingAfterHide.start ( iRandomizedShutdownMs );
}

void CConnectDlg::OnDirectoryChanged ( int iTypeIdx )
Expand All @@ -295,6 +312,12 @@ void CConnectDlg::OnDirectoryChanged ( int iTypeIdx )
RequestServerList();
}

void CConnectDlg::OnTimerKeepPingAfterHide()
{
// Shutdown timer expired - now stop all ping activities
TimerPing.stop();
}

void CConnectDlg::OnTimerReRequestServList()
{
// if the server list is not yet received, retransmit the request for the
Expand Down Expand Up @@ -451,9 +474,13 @@ void CConnectDlg::SetServerList ( const CHostAddress& InetAddr, const CVector<CS

// store the maximum number of clients
pNewListViewItem->setText ( LVC_CLIENTS_MAX_HIDDEN, QString().setNum ( vecServerInfo[iIdx].iMaxNumClients ) );
pNewListViewItem->setData ( LVC_NAME, USER_ROLE_PING_SALT, QRandomGenerator::global()->bounded ( 500 ) ); // random ping salt per server
pNewListViewItem->setData ( LVC_NAME, USER_ROLE_LAST_PING_TIMESTAMP, 0 );

// store host address
pNewListViewItem->setData ( LVC_NAME, Qt::UserRole, CurHostAddress.toString() );
pNewListViewItem->setData ( LVC_NAME, USER_ROLE_HOST_ADDRESS, CurHostAddress.toString() );
pNewListViewItem->setData ( LVC_NAME, USER_ROLE_QHOST_ADDRESS_CACHE, QVariant() ); // cache QHostAddress, will be updated on first ping
pNewListViewItem->setData ( LVC_NAME, USER_ROLE_QHOST_PORT_CACHE, QVariant() ); // cache quint16 port number, will be updated on first ping

// per default expand the list item (if not "show all servers")
if ( bShowAllMusicians )
Expand All @@ -465,7 +492,10 @@ void CConnectDlg::SetServerList ( const CHostAddress& InetAddr, const CVector<CS
// immediately issue the ping measurements and start the ping timer since
// the server list is filled now
OnTimerPing();
TimerPing.start ( PING_UPDATE_TIME_SERVER_LIST_MS );

// in stealth mode a lot of pings are skipped, so the frequency here can be higher
int iPingUpdateInterval = QRandomGenerator::global()->bounded ( 50 ) + 500;
TimerPing.start ( iPingUpdateInterval );
}

void CConnectDlg::SetConnClientsList ( const CHostAddress& InetAddr, const CVector<CChannelInfo>& vecChanInfo )
Expand Down Expand Up @@ -728,7 +758,7 @@ void CConnectDlg::OnConnectClicked()
QTreeWidgetItem* pCurSelTopListItem = GetParentListViewItem ( CurSelListItemList[0] );

// get host address from selected list view item as a string
strSelectedAddress = pCurSelTopListItem->data ( LVC_NAME, Qt::UserRole ).toString();
strSelectedAddress = pCurSelTopListItem->data ( LVC_NAME, USER_ROLE_HOST_ADDRESS ).toString();

// store selected server name
strSelectedServerName = pCurSelTopListItem->text ( LVC_NAME );
Expand Down Expand Up @@ -782,20 +812,95 @@ void CConnectDlg::OnTimerPing()
// we need to ask for the server version only if we have not received it
const bool bNeedVersion = pCurListViewItem->text ( LVC_VERSION ).isEmpty();

// retrieve cached QHostAddress and port from UserData
QVariant varCachedIP = pCurListViewItem->data ( LVC_NAME, USER_ROLE_QHOST_ADDRESS_CACHE );
QVariant varCachedPort = pCurListViewItem->data ( LVC_NAME, USER_ROLE_QHOST_PORT_CACHE );

CHostAddress haServerAddress;

// try to parse host address string which is stored as user data
// in the server list item GUI control element
if ( NetworkUtil().ParseNetworkAddress ( pCurListViewItem->data ( LVC_NAME, Qt::UserRole ).toString(), haServerAddress, bEnableIPv6 ) )
if ( varCachedIP.canConvert<QHostAddress>() && varCachedPort.canConvert<quint16>() && !varCachedIP.isNull() && !varCachedPort.isNull() )
{
// if address is valid, send ping message using a new thread
// Use cached values
haServerAddress.InetAddr = varCachedIP.value<QHostAddress>();
haServerAddress.iPort = varCachedPort.value<quint16>();
}
else
{
// Fallback: parse and cache if not present (should not happen in normal flow)
if ( !NetworkUtil().ParseNetworkAddress ( pCurListViewItem->data ( LVC_NAME, USER_ROLE_HOST_ADDRESS ).toString(),
haServerAddress,
bEnableIPv6 ) )
{
continue; // Skip this server if parsing fails
}

// Cache the parsed values for future use
pCurListViewItem->setData ( LVC_NAME, USER_ROLE_QHOST_ADDRESS_CACHE, QVariant::fromValue ( haServerAddress.InetAddr ) );
pCurListViewItem->setData ( LVC_NAME, USER_ROLE_QHOST_PORT_CACHE, QVariant::fromValue ( haServerAddress.iPort ) );
}

// Get minimum ping time and last ping timestamp
const qint64 iCurrentTime = QDateTime::currentMSecsSinceEpoch();
const int iMinPingTime = pCurListViewItem->text ( LVC_PING_MIN_HIDDEN ).toInt();
const qint64 iLastPingTimestamp = pCurListViewItem->data ( LVC_NAME, USER_ROLE_LAST_PING_TIMESTAMP ).toLongLong();
const int iServerSalt = pCurListViewItem->data ( LVC_NAME, USER_ROLE_PING_SALT ).toInt();
const qint64 iTimeSinceLastPing = iCurrentTime - iLastPingTimestamp;

// Calculate adaptive ping interval based on latency using linear formula:
int iPingInterval;
if ( iMinPingTime == 0 || iMinPingTime > 99999999 )
{
// Never pinged or invalid - always ping to get initial measurement
iPingInterval = 0;
}
else
{
// Calculate adaptive ping interval: base multiplier from latency (15ms→1x, capped at fPingMaxMultiplier),
// combined with geographic (high-latency servers get 3-50% extra variance) and random factors (±15%)
// fPingMaxMultiplier is currently pretty low to keep it similar to the normal constant ping, should be increased in future versions (like
// 8-10)
const float fPingMaxMultiplier = 3.0f;
const float fGeoFactor = 1.0f + ( std::min ( 500, iMinPingTime ) / 100.0f ) * 0.2f; // high ping get more variance
const float fRandomFactor = ( 0.85f + QRandomGenerator::global()->generateDouble() * 0.3f ) * fGeoFactor;
const float fIntervalMultiplier =
std::min ( fPingMaxMultiplier, std::max ( 1.0f, 1.0f + ( iMinPingTime - 15 ) / 25.0f ) ) * fRandomFactor;

iPingInterval = static_cast<int> ( PING_UPDATE_TIME_SERVER_LIST_MS * fIntervalMultiplier );

iPingInterval += iServerSalt;

// Add randomization to prevent any synchronized pings across servers with the same ping
const int iRandomOffsetMs = QRandomGenerator::global()->bounded ( 600 ) - 300; // -300ms to +300ms
iPingInterval += iRandomOffsetMs;
iPingInterval = std::max ( iPingInterval, 500 );

// during shutdown: randomly sent a ping for first 20 to 30 seconds only, can be removed in the future when this mode is used
// by the majority of clients
if ( TimerKeepPingAfterHide.isActive() )
{
const qint64 iTimeSinceHide = iCurrentTime - iKeepPingAfterHideStartTimestamp;
if ( iTimeSinceHide < ( 20000 + QRandomGenerator::global()->bounded ( 10000 ) ) )
{
iPingInterval = 2000 + QRandomGenerator::global()->bounded ( 1000 );
}
}
}

// Skip this server if not enough time has passed since last ping
if ( iTimeSinceLastPing < iPingInterval )
{
continue;
}

pCurListViewItem->setData ( LVC_NAME, USER_ROLE_LAST_PING_TIMESTAMP, qint64 ( iCurrentTime ) );

// if address is valid, send ping message using a new thread
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
QFuture<void> f = QtConcurrent::run ( &CConnectDlg::EmitCLServerListPingMes, this, haServerAddress, bNeedVersion );
Q_UNUSED ( f );
QFuture<void> f = QtConcurrent::run ( &CConnectDlg::EmitCLServerListPingMes, this, haServerAddress, bNeedVersion );
Q_UNUSED ( f );
#else
QtConcurrent::run ( this, &CConnectDlg::EmitCLServerListPingMes, haServerAddress, bNeedVersion );
QtConcurrent::run ( this, &CConnectDlg::EmitCLServerListPingMes, haServerAddress, bNeedVersion );
#endif
}
}
}

Expand Down Expand Up @@ -967,7 +1072,7 @@ QTreeWidgetItem* CConnectDlg::FindListViewItem ( const CHostAddress& InetAddr )
{
// compare the received address with the user data string of the
// host address by a string compare
if ( !lvwServers->topLevelItem ( iIdx )->data ( LVC_NAME, Qt::UserRole ).toString().compare ( InetAddr.toString() ) )
if ( !lvwServers->topLevelItem ( iIdx )->data ( LVC_NAME, USER_ROLE_HOST_ADDRESS ).toString().compare ( InetAddr.toString() ) )
{
return lvwServers->topLevelItem ( iIdx );
}
Expand Down
20 changes: 19 additions & 1 deletion src/connectdlg.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
// transmitted until it is received
#define SERV_LIST_REQ_UPDATE_TIME_MS 2000 // ms

// defines the time interval it will keep pinging servers after the dialog was hidden (randomized +/- 20%)
#define KEEP_PING_RUNNING_AFTER_HIDE_MS ( 1000 * 180 )

Q_DECLARE_METATYPE ( QHostAddress )

/* Classes ********************************************************************/
class CConnectDlg : public CBaseDlg, private Ui_CConnectDlgBase
{
Expand Down Expand Up @@ -80,6 +85,16 @@ class CConnectDlg : public CBaseDlg, private Ui_CConnectDlgBase
LVC_COLUMNS // total number of columns
};

// those are the custom user data fields, all stored in the UserRole of the list view item (LVC_NAME)
enum EColumnPingNameUserRoles
{
USER_ROLE_HOST_ADDRESS = Qt::UserRole, // QString: CHostAddress as string
USER_ROLE_QHOST_ADDRESS_CACHE, // QHostAddress: cache QHostAddress, will be updated on first ping
USER_ROLE_QHOST_PORT_CACHE, // quint16: cache port number, will be updated on first ping
USER_ROLE_LAST_PING_TIMESTAMP, // qint64: timestamp of last ping measurement
USER_ROLE_PING_SALT // int: random ping salt per server
};

virtual void showEvent ( QShowEvent* );
virtual void hideEvent ( QHideEvent* );

Expand All @@ -91,10 +106,12 @@ class CConnectDlg : public CBaseDlg, private Ui_CConnectDlgBase
void RequestServerList();
void EmitCLServerListPingMes ( const CHostAddress& haServerAddress, const bool bNeedVersion );
void UpdateDirectoryComboBox();

#
CClientSettings* pSettings;

QTimer TimerPing;
QTimer TimerKeepPingAfterHide;
qint64 iKeepPingAfterHideStartTimestamp; // timestamp when keeping pings after hide started
QTimer TimerReRequestServList;
QTimer TimerInitialSort;
CHostAddress haDirectoryAddress;
Expand All @@ -118,6 +135,7 @@ public slots:
void OnConnectClicked();
void OnDeleteServerAddrClicked();
void OnTimerPing();
void OnTimerKeepPingAfterHide();
void OnTimerReRequestServList();

signals:
Expand Down