Skip to content

Commit b55f6f9

Browse files
committed
feat: Adding transport RTT to UI stats and using exponential moving average for calculations (#528)
1 parent 9c00f15 commit b55f6f9

File tree

1 file changed

+34
-35
lines changed

1 file changed

+34
-35
lines changed

Assets/BossRoom/Scripts/Shared/Net/NetworkStats.cs

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,35 +16,47 @@ namespace Unity.Multiplayer.Samples.BossRoom
1616
[RequireComponent(typeof(NetworkObject))]
1717
public class NetworkStats : NetworkBehaviour
1818
{
19+
// For a value like RTT an exponential moving average is a better indication of the current rtt and fluctuates less.
20+
struct ExponentialMovingAverageCalculator
21+
{
22+
readonly float m_Alpha;
23+
float m_Average;
24+
25+
public float Average => m_Average;
26+
27+
public ExponentialMovingAverageCalculator(float average)
28+
{
29+
m_Alpha = 2f / (k_MaxWindowSize + 1);
30+
m_Average = average;
31+
}
32+
33+
public float NextValue(float value) => m_Average = (value - m_Average) * m_Alpha + m_Average;
34+
}
35+
1936
// RTT
2037
// Client sends a ping RPC to the server and starts it's timer.
2138
// The server receives the ping and sends a pong response to the client.
2239
// The client receives that pong response and stops its time.
2340
// The RPC value is using a moving average, so we don't have a value that moves too much, but is still reactive to RTT changes.
24-
//
25-
// Note: when adding more stats, it might be worth it to abstract these in their own classes instead of having a bunch
26-
// of attributes floating around.
2741

28-
public float LastRTT { get; private set; }
42+
const int k_MaxWindowSizeSeconds = 3; // it should take x seconds for the value to react to change
43+
const float k_PingIntervalSeconds = 0.1f;
44+
const float k_MaxWindowSize = k_MaxWindowSizeSeconds / k_PingIntervalSeconds;
45+
46+
ExponentialMovingAverageCalculator m_BossRoomRTT = new ExponentialMovingAverageCalculator(0);
47+
ExponentialMovingAverageCalculator m_UtpRTT = new ExponentialMovingAverageCalculator(0);
2948

30-
[SerializeField]
31-
[Tooltip("The interval to send ping RPCs to calculate the RTT. The bigger the number, the less reactive the stat will be to RTT changes")]
32-
float m_PingIntervalSeconds = 0.1f;
3349
float m_LastPingTime;
3450
Text m_TextStat;
3551
Text m_TextHostType;
3652

3753
// When receiving pong client RPCs, we need to know when the initiating ping sent it so we can calculate its individual RTT
3854
int m_CurrentRTTPingId;
3955

40-
Queue<float> m_MovingWindow = new Queue<float>();
41-
const int k_MaxWindowSizeSeconds = 3; // it should take x seconds for the value to react to change
42-
float m_MaxWindowSize => k_MaxWindowSizeSeconds / m_PingIntervalSeconds;
4356
Dictionary<int, float> m_PingHistoryStartTimes = new Dictionary<int, float>();
4457

4558
ClientRpcParams m_PongClientParams;
4659

47-
4860
public override void OnNetworkSpawn()
4961
{
5062
bool isClientOnly = IsClient && !IsServer;
@@ -53,10 +65,12 @@ public override void OnNetworkSpawn()
5365
Destroy(this);
5466
return;
5567
}
68+
5669
if (IsOwner)
5770
{
5871
CreateNetworkStatsText();
5972
}
73+
6074
m_PongClientParams = new ClientRpcParams() { Send = new ClientRpcSendParams() { TargetClientIds = new[] { OwnerClientId } } };
6175
}
6276

@@ -71,7 +85,7 @@ void CreateNetworkStatsText()
7185
InitializeTextLine("No Stat", out m_TextStat);
7286
}
7387

74-
private void InitializeTextLine(string defaultText, out Text textComponent)
88+
void InitializeTextLine(string defaultText, out Text textComponent)
7589
{
7690
GameObject rootGO = new GameObject("UI Stat Text");
7791
textComponent = rootGO.AddComponent<Text>();
@@ -91,19 +105,21 @@ void FixedUpdate()
91105
var textToDisplay = string.Empty;
92106
if (!IsServer)
93107
{
94-
if (Time.realtimeSinceStartup - m_LastPingTime > m_PingIntervalSeconds)
108+
if (Time.realtimeSinceStartup - m_LastPingTime > k_PingIntervalSeconds)
95109
{
96110
// We could have had a ping/pong where the ping sends the pong and the pong sends the ping. Issue with this
97111
// is the higher the latency, the lower the sampling would be. We need pings to be sent at a regular interval
98112
PingServerRPC(m_CurrentRTTPingId);
99113
m_PingHistoryStartTimes[m_CurrentRTTPingId] = Time.realtimeSinceStartup;
100114
m_CurrentRTTPingId++;
101115
m_LastPingTime = Time.realtimeSinceStartup;
116+
117+
m_UtpRTT.NextValue(NetworkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(NetworkManager.ServerClientId));
102118
}
103119

104120
if (m_TextStat != null)
105121
{
106-
textToDisplay = $"{textToDisplay}RTT: {(LastRTT * 1000f).ToString()} ms ";
122+
textToDisplay = $"{textToDisplay}RTT: {(m_BossRoomRTT.Average * 1000).ToString("0")} ms;\nUTP RTT {m_UtpRTT.Average.ToString("0")} ms";
107123
}
108124
}
109125

@@ -118,36 +134,18 @@ void FixedUpdate()
118134
}
119135
}
120136

121-
122137
[ServerRpc]
123-
public void PingServerRPC(int pingId, ServerRpcParams serverParams = default)
138+
void PingServerRPC(int pingId, ServerRpcParams serverParams = default)
124139
{
125140
PongClientRPC(pingId, m_PongClientParams);
126141
}
127142

128143
[ClientRpc]
129-
public void PongClientRPC(int pingId, ClientRpcParams clientParams = default)
144+
void PongClientRPC(int pingId, ClientRpcParams clientParams = default)
130145
{
131146
var startTime = m_PingHistoryStartTimes[pingId];
132147
m_PingHistoryStartTimes.Remove(pingId);
133-
m_MovingWindow.Enqueue(Time.realtimeSinceStartup - startTime);
134-
UpdateRTTSlidingWindowAverage();
135-
}
136-
137-
void UpdateRTTSlidingWindowAverage()
138-
{
139-
if (m_MovingWindow.Count > m_MaxWindowSize)
140-
{
141-
m_MovingWindow.Dequeue();
142-
}
143-
144-
float rttSum = 0;
145-
foreach (var singleRTT in m_MovingWindow)
146-
{
147-
rttSum += singleRTT;
148-
}
149-
150-
LastRTT = rttSum / m_MaxWindowSize;
148+
m_BossRoomRTT.NextValue(Time.realtimeSinceStartup - startTime);
151149
}
152150

153151
public override void OnNetworkDespawn()
@@ -156,6 +154,7 @@ public override void OnNetworkDespawn()
156154
{
157155
Destroy(m_TextStat.gameObject);
158156
}
157+
159158
if (m_TextHostType != null)
160159
{
161160
Destroy(m_TextHostType.gameObject);

0 commit comments

Comments
 (0)