Skip to content

Commit d755626

Browse files
committed
Made the library much reactive to network health changes.
Refactored the way the library keeps the server input frame buffer count as smaller as possible depending on the net conditions. Now it uses proper statistics from the NetworkInterface, instead to guess it based on the missing inputs. Exposes new API to get the peer: - latency jitter - packet loss
1 parent 95b8c27 commit d755626

10 files changed

+303
-446
lines changed

core/net_utilities.h

Lines changed: 16 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@
4646

4747
NS_NAMESPACE_BEGIN
4848

49+
namespace MathFunc {
50+
template <typename F>
51+
F lerp(F a, F b, F alpha) {
52+
return a + alpha * (b - a);
53+
}
54+
} //namespace MathFunc
55+
4956
namespace MapFunc {
5057

5158
template <class K, class V>
@@ -254,174 +261,6 @@ struct ListenerHandle {
254261
};
255262
inline static const ListenerHandle nulllistenerhandle = { 0 };
256263

257-
template <class T>
258-
class StatisticalRingBuffer {
259-
LocalVector<T> data;
260-
uint32_t index = 0;
261-
262-
T avg_sum = 0;
263-
264-
public:
265-
StatisticalRingBuffer(uint32_t p_size, T p_default);
266-
void resize(uint32_t p_size, T p_default);
267-
void reset(T p_default);
268-
269-
void push(T p_value);
270-
271-
/// Maximum value.
272-
T max() const;
273-
274-
/// Minumum value.
275-
T min(uint32_t p_consider_last = UINT32_MAX) const;
276-
277-
/// Median value.
278-
T average() const;
279-
T average_rounded() const;
280-
281-
T get_deviation(T p_mean) const;
282-
283-
private:
284-
// Used to avoid accumulate precision loss.
285-
void force_recompute_avg_sum();
286-
};
287-
288-
template <class T>
289-
StatisticalRingBuffer<T>::StatisticalRingBuffer(uint32_t p_size, T p_default) {
290-
resize(p_size, p_default);
291-
}
292-
293-
template <class T>
294-
void StatisticalRingBuffer<T>::resize(uint32_t p_size, T p_default) {
295-
data.resize(p_size);
296-
297-
reset(p_default);
298-
}
299-
300-
template <class T>
301-
void StatisticalRingBuffer<T>::reset(T p_default) {
302-
for (uint32_t i = 0; i < data.size(); i += 1) {
303-
data[i] = p_default;
304-
}
305-
306-
index = 0;
307-
force_recompute_avg_sum();
308-
}
309-
310-
template <class T>
311-
void StatisticalRingBuffer<T>::push(T p_value) {
312-
avg_sum -= data[index];
313-
avg_sum += p_value;
314-
data[index] = p_value;
315-
316-
index = (index + 1) % data.size();
317-
if (index == 0) {
318-
// Each cycle recompute the sum.
319-
force_recompute_avg_sum();
320-
}
321-
}
322-
323-
template <class T>
324-
T StatisticalRingBuffer<T>::max() const {
325-
CRASH_COND(data.size() == 0);
326-
327-
T a = data[0];
328-
for (uint32_t i = 1; i < data.size(); i += 1) {
329-
a = MAX(a, data[i]);
330-
}
331-
return a;
332-
}
333-
334-
template <class T>
335-
T StatisticalRingBuffer<T>::min(uint32_t p_consider_last) const {
336-
CRASH_COND(data.size() == 0);
337-
p_consider_last = MIN(p_consider_last, data.size());
338-
339-
const uint32_t youngest = (index == 0 ? data.size() : index) - 1;
340-
const uint32_t oldest = (index + (data.size() - p_consider_last)) % data.size();
341-
342-
T a = data[oldest];
343-
344-
uint32_t i = oldest;
345-
do {
346-
i = (i + 1) % data.size();
347-
a = MIN(a, data[i]);
348-
} while (i != youngest);
349-
350-
return a;
351-
}
352-
353-
template <class T>
354-
T StatisticalRingBuffer<T>::average() const {
355-
CRASH_COND(data.size() == 0);
356-
357-
#ifdef DEBUG_ENABLED
358-
T a = data[0];
359-
for (uint32_t i = 1; i < data.size(); i += 1) {
360-
a += data[i];
361-
}
362-
a = a / T(data.size());
363-
T b = avg_sum / T(data.size());
364-
const T difference = a > b ? a - b : b - a;
365-
ERR_FAIL_COND_V_MSG(difference > (std::numeric_limits<T>::epsilon() * 4.0), b, "The `avg_sum` accumulated a sensible precision loss: " + rtos(difference));
366-
return b;
367-
#else
368-
// Divide it by the buffer size is wrong when the buffer is not yet fully
369-
// initialized. However, this is wrong just for the first run.
370-
// I'm leaving it as is because solve it mean do more operations. All this
371-
// just to get the right value for the first few frames.
372-
return avg_sum / T(data.size());
373-
#endif
374-
}
375-
376-
template <class T>
377-
T StatisticalRingBuffer<T>::average_rounded() const {
378-
CRASH_COND(data.size() == 0);
379-
380-
#ifdef DEBUG_ENABLED
381-
T a = data[0];
382-
for (uint32_t i = 1; i < data.size(); i += 1) {
383-
a += data[i];
384-
}
385-
a = round(double(a) / double(data.size()));
386-
T b = round(double(avg_sum) / double(data.size()));
387-
const T difference = a > b ? a - b : b - a;
388-
ERR_FAIL_COND_V_MSG(difference > (std::numeric_limits<T>::epsilon() * 4.0), b, "The `avg_sum` accumulated a sensible precision loss: " + rtos(difference));
389-
return b;
390-
#else
391-
// Divide it by the buffer size is wrong when the buffer is not yet fully
392-
// initialized. However, this is wrong just for the first run.
393-
// I'm leaving it as is because solve it mean do more operations. All this
394-
// just to get the right value for the first few frames.
395-
return round(double(avg_sum) / double(data.size()));
396-
#endif
397-
}
398-
399-
template <class T>
400-
T StatisticalRingBuffer<T>::get_deviation(T p_mean) const {
401-
if (data.size() <= 0) {
402-
return T();
403-
}
404-
405-
double r = 0;
406-
for (uint32_t i = 0; i < data.size(); i += 1) {
407-
r += pow(double(data[i]) - double(p_mean), 2.0);
408-
}
409-
410-
return sqrt(r / double(data.size()));
411-
}
412-
413-
template <class T>
414-
void StatisticalRingBuffer<T>::force_recompute_avg_sum() {
415-
#ifdef DEBUG_ENABLED
416-
// This class is not supposed to be used with 0 size.
417-
CRASH_COND(data.size() <= 0);
418-
#endif
419-
avg_sum = data[0];
420-
for (uint32_t i = 1; i < data.size(); i += 1) {
421-
avg_sum += data[i];
422-
}
423-
}
424-
425264
// These data are used by the server and are never synchronized.
426265
struct PeerAuthorityData {
427266
// Used to know if the peer is enabled.
@@ -451,7 +290,7 @@ struct PeerData {
451290
/// 100ms; the jitter will be 0.
452291
/// - If the time difference is either 150ms or 100ms, the jitter will tend
453292
/// towards 50ms.
454-
float average_jitter_in_ms = 0.0;
293+
float latency_jitter_ms = 0.0;
455294

456295
public:
457296
// In ms
@@ -463,11 +302,11 @@ struct PeerData {
463302
void set_compressed_latency(std::uint8_t p_compressed_latency) { compressed_latency = p_compressed_latency; }
464303
std::uint8_t get_compressed_latency() const { return compressed_latency; }
465304

466-
void set_out_packet_loss_percentage(float p_packet_loss) { out_packet_loss_percentage = p_packet_loss; }
305+
void set_out_packet_loss_percentage(float p_packet_loss) { out_packet_loss_percentage = std::clamp(p_packet_loss, 0.0f, 1.0f); }
467306
float get_out_packet_loss_percentage() const { return out_packet_loss_percentage; }
468307

469-
void set_average_jitter_in_ms(float p_jitter_ms) { average_jitter_in_ms = p_jitter_ms; }
470-
float get_average_jitter_in_ms() const { return average_jitter_in_ms; }
308+
void set_latency_jitter_ms(float p_jitter_ms) { latency_jitter_ms = p_jitter_ms; }
309+
float get_latency_jitter_ms() const { return latency_jitter_ms; }
471310

472311
void make_controller();
473312
PeerNetworkedController *get_controller() {
@@ -485,8 +324,11 @@ struct PeerServerData {
485324
// For new peers a full snapshot is needed.
486325
bool need_full_snapshot = true;
487326

488-
// How much time (seconds) from the latest net_stats update.
489-
float netstats_update_sec = 0.0;
327+
// How much time (seconds) from the latest latency update sent via snapshot.
328+
float latency_update_via_snapshot_sec = 0.0;
329+
330+
// How much time (seconds) from the latest update sent to the client.
331+
float netstats_peer_update_sec = 0.0;
490332
};
491333

492334
struct SyncGroup {

core/peer_networked_controller.cpp

Lines changed: 4 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -123,30 +123,6 @@ int PeerNetworkedController::get_max_redundant_inputs() const {
123123
return max_redundant_inputs;
124124
}
125125

126-
void PeerNetworkedController::set_network_traced_frames(int p_size) {
127-
network_traced_frames = p_size;
128-
}
129-
130-
int PeerNetworkedController::get_network_traced_frames() const {
131-
return network_traced_frames;
132-
}
133-
134-
void PeerNetworkedController::set_min_frames_delay(int p_val) {
135-
min_frames_delay = p_val;
136-
}
137-
138-
int PeerNetworkedController::get_min_frames_delay() const {
139-
return min_frames_delay;
140-
}
141-
142-
void PeerNetworkedController::set_max_frames_delay(int p_val) {
143-
max_frames_delay = p_val;
144-
}
145-
146-
int PeerNetworkedController::get_max_frames_delay() const {
147-
return max_frames_delay;
148-
}
149-
150126
FrameIndex PeerNetworkedController::get_current_frame_index() const {
151127
ENSURE_V(controller, FrameIndex::NONE);
152128
return controller->get_current_frame_index();
@@ -747,11 +723,8 @@ bool RemotelyControlledController::receive_inputs(const Vector<uint8_t> &p_data)
747723
}
748724

749725
ServerController::ServerController(
750-
PeerNetworkedController *p_peer_controller,
751-
int p_traced_frames) :
752-
RemotelyControlledController(p_peer_controller),
753-
network_watcher(p_traced_frames, 0),
754-
consecutive_input_watcher(p_traced_frames, 0) {
726+
PeerNetworkedController *p_peer_controller) :
727+
RemotelyControlledController(p_peer_controller) {
755728
}
756729

757730
void ServerController::process(double p_delta) {
@@ -765,7 +738,6 @@ void ServerController::process(double p_delta) {
765738
consecutive_inputs += 1;
766739
}
767740
}
768-
consecutive_input_watcher.push(consecutive_inputs);
769741
}
770742
}
771743

@@ -777,35 +749,10 @@ void ServerController::on_peer_update(bool p_peer_enabled) {
777749

778750
// ~~ Reset everything to avoid accumulate old data. ~~
779751
RemotelyControlledController::on_peer_update(p_peer_enabled);
780-
781-
additional_fps_notif_timer = 0.0;
782-
previous_frame_received_timestamp = UINT32_MAX;
783-
network_watcher.reset(0.0);
784-
consecutive_input_watcher.reset(0.0);
785752
}
786753

787754
void ServerController::set_frame_input(const FrameInput &p_frame_snapshot, bool p_first_input) {
788-
// If `previous_frame_received_timestamp` is bigger: the controller was
789-
// disabled, so nothing to do.
790-
if (previous_frame_received_timestamp < p_frame_snapshot.received_timestamp) {
791-
const uint32_t frame_delta_ms = peer_controller->scene_synchronizer->get_fixed_frame_delta() * 1000.0;
792-
793-
const uint32_t receival_time = p_frame_snapshot.received_timestamp - previous_frame_received_timestamp;
794-
const uint32_t network_time = receival_time > frame_delta_ms ? receival_time - frame_delta_ms : 0;
795-
796-
network_watcher.push(network_time);
797-
}
798-
799755
RemotelyControlledController::set_frame_input(p_frame_snapshot, p_first_input);
800-
801-
if (p_first_input) {
802-
// Reset the watcher, as this is the first input.
803-
network_watcher.reset(0);
804-
consecutive_input_watcher.reset(0.0);
805-
previous_frame_received_timestamp = UINT32_MAX;
806-
} else {
807-
previous_frame_received_timestamp = p_frame_snapshot.received_timestamp;
808-
}
809756
}
810757

811758
void ServerController::notify_send_state() {
@@ -840,50 +787,9 @@ int ceil_with_tolerance(double p_value, double p_tolerance) {
840787
return std::ceil(p_value - p_tolerance);
841788
}
842789

843-
std::int8_t ServerController::compute_client_tick_rate_distance_to_optimal() {
844-
const float min_frames_delay = peer_controller->get_min_frames_delay();
845-
const float max_frames_delay = peer_controller->get_max_frames_delay();
846-
const double fixed_frame_delta = peer_controller->scene_synchronizer->get_fixed_frame_delta();
847-
848-
// `worst_receival_time` is in ms and indicates the maximum time passed to receive a consecutive
849-
// input in the last `network_traced_frames` frames.
850-
const std::uint32_t worst_receival_time_ms = network_watcher.max();
851-
852-
const double worst_receival_time = double(worst_receival_time_ms) / 1000.0;
853-
854-
const int optimal_frame_delay_unclamped = ceil_with_tolerance(
855-
worst_receival_time / fixed_frame_delta,
856-
fixed_frame_delta * 0.05); // Tolerance of 5% of frame time.
857-
858-
const int optimal_frame_delay = CLAMP(optimal_frame_delay_unclamped, min_frames_delay, max_frames_delay);
859-
860-
const int consecutive_inputs = consecutive_input_watcher.average_rounded();
861-
862-
const std::int8_t distance_to_optimal = CLAMP(optimal_frame_delay - consecutive_inputs, INT8_MIN, INT8_MAX);
863-
864-
#ifdef DEBUG_ENABLED
865-
const bool debug = ProjectSettings::get_singleton()->get_setting("NetworkSynchronizer/debug_server_speedup");
866-
const int current_frame_delay = consecutive_inputs;
867-
if (debug) {
868-
SceneSynchronizerDebugger::singleton()->print(
869-
INFO,
870-
"Worst receival time (ms): `" + std::to_string(worst_receival_time_ms) +
871-
"` Optimal frame delay: `" + std::to_string(optimal_frame_delay) +
872-
"` Current frame delay: `" + std::to_string(current_frame_delay) +
873-
"` Distance to optimal: `" + std::to_string(distance_to_optimal) +
874-
"`",
875-
"NetController",
876-
true);
877-
}
878-
peer_controller->event_client_speedup_adjusted.broadcast(worst_receival_time_ms, optimal_frame_delay, current_frame_delay, distance_to_optimal);
879-
#endif
880-
881-
return distance_to_optimal;
882-
}
883-
884790
AutonomousServerController::AutonomousServerController(
885791
PeerNetworkedController *p_peer_controller) :
886-
ServerController(p_peer_controller, 1) {
792+
ServerController(p_peer_controller) {
887793
}
888794

889795
bool AutonomousServerController::receive_inputs(const Vector<uint8_t> &p_data) {
@@ -1403,7 +1309,7 @@ int DollController::fetch_optimal_queued_inputs() const {
14031309
//
14041310
// TODO: At the moment this value is fixed to the min_frame_delay, but at some
14051311
// point we will want to change this value dynamically depending on packet loss.
1406-
return peer_controller->get_min_frames_delay();
1312+
return peer_controller->scene_synchronizer->get_min_server_input_buffer_size();
14071313
}
14081314

14091315
bool DollController::fetch_next_input(double p_delta) {

0 commit comments

Comments
 (0)