From c006f7121047e5f1f660f2f8411c726411c35728 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Fri, 30 Aug 2024 19:43:40 -0400 Subject: [PATCH] add some safety asserts to help catch when MaxClients is set too low for tests, channelIndex --- include/yojimbo_config.h | 3 +- include/yojimbo_constants.h | 2 +- include/yojimbo_network_info.h | 6 +- reliable/reliable.c | 120 +++++++++++++++++++++++++++++++-- reliable/reliable.h | 11 ++- source/yojimbo_base_client.cpp | 12 ++++ source/yojimbo_base_server.cpp | 27 ++++++++ source/yojimbo_server.cpp | 3 +- test.cpp | 34 +++++----- 9 files changed, 190 insertions(+), 28 deletions(-) diff --git a/include/yojimbo_config.h b/include/yojimbo_config.h index 541d1a30..d4a2f2e9 100644 --- a/include/yojimbo_config.h +++ b/include/yojimbo_config.h @@ -170,8 +170,9 @@ namespace yojimbo ConnectionConfig() { - numChannels = 1; + numChannels = 2; maxPacketSize = 8 * 1024; + channel[0].type = CHANNEL_TYPE_RELIABLE_ORDERED; } }; diff --git a/include/yojimbo_constants.h b/include/yojimbo_constants.h index 3833c631..147c910f 100644 --- a/include/yojimbo_constants.h +++ b/include/yojimbo_constants.h @@ -31,7 +31,7 @@ namespace yojimbo { const int MaxClients = 64; ///< The maximum number of clients supported by this library. You can increase this if you want, but this library is designed around patterns that work best for [2,64] player games. If your game has less than 64 clients, reducing this will save memory. - const int MaxChannels = 64; ///< The maximum number of message channels supported by this library. If you need less than 64 channels per-packet, reducing this will save memory. + const int MaxChannels = 2; ///< The maximum number of message channels supported by this library. If you need less than 64 channels per-packet, reducing this will save memory. const int KeyBytes = 32; ///< Size of encryption key for dedicated client/server in bytes. Must be equal to key size for libsodium encryption primitive. Do not change. diff --git a/include/yojimbo_network_info.h b/include/yojimbo_network_info.h index 7195a192..756dcb53 100644 --- a/include/yojimbo_network_info.h +++ b/include/yojimbo_network_info.h @@ -36,7 +36,11 @@ namespace yojimbo struct NetworkInfo { - float RTT; ///< Round trip time estimate (milliseconds). + float RTT; ///< Round trip time estimate (milliseconds). Exponentially smoothed average tracking most recent RTT value. + float minRTT; ///< Minimum RTT seen over the last n samples (see rtt_history_size in reliable config). This is a more stable and accurate RTT value under typical Wi-Fi jitter. + float maxRTT; ///< Maximum RTT seen over the last n samples. + float averageRTT; ///< Average RTT seen over the last n samples. + float jitter; ///< Jitter relative to min RTT over the last n samples. float packetLoss; ///< Packet loss percent. float sentBandwidth; ///< Sent bandwidth (kbps). float receivedBandwidth; ///< Received bandwidth (kbps). diff --git a/reliable/reliable.c b/reliable/reliable.c index c8638020..4733a1c0 100644 --- a/reliable/reliable.c +++ b/reliable/reliable.c @@ -508,6 +508,10 @@ struct reliable_endpoint_t struct reliable_config_t config; double time; float rtt; + float rtt_min; + float rtt_max; + float rtt_avg; + float jitter; float packet_loss; float sent_bandwidth_kbps; float received_bandwidth_kbps; @@ -515,6 +519,7 @@ struct reliable_endpoint_t int num_acks; uint16_t * acks; uint16_t sequence; + float * rtt_history_buffer; struct reliable_sequence_buffer_t * sent_packets; struct reliable_sequence_buffer_t * received_packets; struct reliable_sequence_buffer_t * fragment_reassembly; @@ -556,9 +561,10 @@ void reliable_default_config( struct reliable_config_t * config ) config->received_packets_buffer_size = 256; config->fragment_reassembly_buffer_size = 64; config->rtt_smoothing_factor = 0.0025f; + config->rtt_history_size = 512; config->packet_loss_smoothing_factor = 0.1f; config->bandwidth_smoothing_factor = 0.1f; - config->packet_header_size = 28; // note: UDP over IPv4 = 20 + 8 bytes, UDP over IPv6 = 40 + 8 bytes + config->packet_header_size = 28; // note: UDP over IPv4 = 20 + 8 bytes, UDP over IPv6 = 40 + 8 bytes } struct reliable_endpoint_t * reliable_endpoint_create( struct reliable_config_t * config, double time ) @@ -574,6 +580,7 @@ struct reliable_endpoint_t * reliable_endpoint_create( struct reliable_config_t reliable_assert( config->received_packets_buffer_size > 0 ); reliable_assert( config->transmit_packet_function != NULL ); reliable_assert( config->process_packet_function != NULL ); + reliable_assert( config->rtt_history_size > 0 ); void * allocator_context = config->allocator_context; void * (*allocate_function)(void*,size_t) = config->allocate_function; @@ -601,7 +608,7 @@ struct reliable_endpoint_t * reliable_endpoint_create( struct reliable_config_t endpoint->config = *config; endpoint->time = time; - endpoint->acks = (uint16_t*) allocate_function( allocator_context, config->ack_buffer_size * sizeof( uint16_t ) ); + endpoint->acks = (uint16_t*) allocate_function( allocator_context, config->ack_buffer_size * sizeof(uint16_t) ); endpoint->sent_packets = reliable_sequence_buffer_create( config->sent_packets_buffer_size, sizeof( struct reliable_sent_packet_data_t ), @@ -621,7 +628,14 @@ struct reliable_endpoint_t * reliable_endpoint_create( struct reliable_config_t allocate_function, free_function ); - memset( endpoint->acks, 0, config->ack_buffer_size * sizeof( uint16_t ) ); + endpoint->rtt_history_buffer = (float*) allocate_function( allocator_context, config->rtt_history_size * sizeof(float) ); + + for ( int i = 0; i < config->rtt_history_size; i++ ) + { + endpoint->rtt_history_buffer[i] = -1.0f; + } + + memset( endpoint->acks, 0, config->ack_buffer_size * sizeof(uint16_t) ); return endpoint; } @@ -632,6 +646,8 @@ void reliable_endpoint_destroy( struct reliable_endpoint_t * endpoint ) reliable_assert( endpoint->acks ); reliable_assert( endpoint->sent_packets ); reliable_assert( endpoint->received_packets ); + reliable_assert( endpoint->fragment_reassembly ); + reliable_assert( endpoint->rtt_history_buffer ); int i; for ( i = 0; i < endpoint->config.fragment_reassembly_buffer_size; ++i ) @@ -652,6 +668,8 @@ void reliable_endpoint_destroy( struct reliable_endpoint_t * endpoint ) reliable_sequence_buffer_destroy( endpoint->received_packets ); reliable_sequence_buffer_destroy( endpoint->fragment_reassembly ); + endpoint->free_function( endpoint->allocator_context, endpoint->rtt_history_buffer ); + endpoint->free_function( endpoint->allocator_context, endpoint ); } @@ -1141,8 +1159,14 @@ void reliable_endpoint_receive_packet( struct reliable_endpoint_t * endpoint, ui endpoint->counters[RELIABLE_ENDPOINT_COUNTER_NUM_PACKETS_ACKED]++; sent_packet_data->acked = 1; - float rtt = (float) ( endpoint->time - sent_packet_data->time ) * 1000.0f; + const float rtt = (float) ( endpoint->time - sent_packet_data->time ) * 1000.0f; + reliable_assert( rtt >= 0.0 ); + + int index = ack_sequence % endpoint->config.rtt_history_size; + + endpoint->rtt_history_buffer[index] = rtt; + if ( ( endpoint->rtt == 0.0f && rtt > 0.0f ) || fabs( endpoint->rtt - rtt ) < 0.00001 ) { endpoint->rtt = rtt; @@ -1320,7 +1344,67 @@ void reliable_endpoint_update( struct reliable_endpoint_t * endpoint, double tim reliable_assert( endpoint ); endpoint->time = time; - + + // calculate min and max rtt + { + float min_rtt = 10000.0f; + float max_rtt = 0.0f; + float sum_rtt = 0.0f; + int count = 0; + for ( int i = 0; i < endpoint->config.rtt_history_size; i++ ) + { + const float rtt = endpoint->rtt_history_buffer[i]; + if ( rtt >= 0.0f ) + { + if ( rtt < min_rtt ) + { + min_rtt = rtt; + } + if ( rtt > max_rtt ) + { + max_rtt = rtt; + } + sum_rtt += rtt; + count++; + } + } + if ( min_rtt == 10000.0f ) + { + min_rtt = 0.0f; + } + endpoint->rtt_min = min_rtt; + endpoint->rtt_max = max_rtt; + if ( count > 0 ) + { + endpoint->rtt_avg = sum_rtt / (float)count; + } + else + { + endpoint->rtt_avg = 0.0f; + } + } + + // calculate jitter + { + float sum = 0.0f; + int count = 0; + for ( int i = 0; i < endpoint->config.rtt_history_size; i++ ) + { + if ( endpoint->rtt_history_buffer[i] >= 0.0f ) + { + sum += endpoint->rtt_history_buffer[i]; + } + } + if ( count > 0 ) + { + endpoint->jitter = sum / (float)count; + } + else + { + endpoint->jitter = 0.0f; + } + } + // calculate packet loss { uint32_t base_sequence = ( endpoint->sent_packets->sequence - endpoint->config.sent_packets_buffer_size + 1 ) + 0xFFFF; @@ -1478,6 +1562,30 @@ float reliable_endpoint_rtt( struct reliable_endpoint_t * endpoint ) return endpoint->rtt; } +float reliable_endpoint_rtt_min( struct reliable_endpoint_t * endpoint ) +{ + reliable_assert( endpoint ); + return endpoint->rtt_min; +} + +float reliable_endpoint_rtt_max( struct reliable_endpoint_t * endpoint ) +{ + reliable_assert( endpoint ); + return endpoint->rtt_max; +} + +float reliable_endpoint_rtt_avg( struct reliable_endpoint_t * endpoint ) +{ + reliable_assert( endpoint ); + return endpoint->rtt_avg; +} + +float reliable_endpoint_jitter( struct reliable_endpoint_t * endpoint ) +{ + reliable_assert( endpoint ); + return endpoint->jitter; +} + float reliable_endpoint_packet_loss( struct reliable_endpoint_t * endpoint ) { reliable_assert( endpoint ); @@ -2382,7 +2490,7 @@ void test_fragment_cleanup() void reliable_test() { - //while ( 1 ) + // while ( 1 ) { RUN_TEST( test_endian ); RUN_TEST( test_sequence_buffer ); diff --git a/reliable/reliable.h b/reliable/reliable.h index 63b392a4..2aafb07f 100644 --- a/reliable/reliable.h +++ b/reliable/reliable.h @@ -68,7 +68,7 @@ #define RELIABLE_ENDPOINT_NUM_COUNTERS 10 #define RELIABLE_MAX_PACKET_HEADER_BYTES 9 -#define RELIABLE_FRAGMENT_HEADER_BYTES 5 +#define RELIABLE_FRAGMENT_HEADER_BYTES 5 #define RELIABLE_LOG_LEVEL_NONE 0 #define RELIABLE_LOG_LEVEL_ERROR 1 @@ -107,6 +107,7 @@ struct reliable_config_t int received_packets_buffer_size; int fragment_reassembly_buffer_size; float rtt_smoothing_factor; + int rtt_history_size; float packet_loss_smoothing_factor; float bandwidth_smoothing_factor; int packet_header_size; @@ -139,6 +140,14 @@ void reliable_endpoint_update( struct reliable_endpoint_t * endpoint, double tim float reliable_endpoint_rtt( struct reliable_endpoint_t * endpoint ); +float reliable_endpoint_rtt_min( struct reliable_endpoint_t * endpoint ); + +float reliable_endpoint_rtt_max( struct reliable_endpoint_t * endpoint ); + +float reliable_endpoint_rtt_avg( struct reliable_endpoint_t * endpoint ); + +float reliable_endpoint_jitter( struct reliable_endpoint_t * endpoint ); + float reliable_endpoint_packet_loss( struct reliable_endpoint_t * endpoint ); void reliable_endpoint_bandwidth( struct reliable_endpoint_t * endpoint, float * sent_bandwidth_kbps, float * received_bandwidth_kbps, float * acked_bandwidth_kpbs ); diff --git a/source/yojimbo_base_client.cpp b/source/yojimbo_base_client.cpp index 01c8efeb..adb611d6 100644 --- a/source/yojimbo_base_client.cpp +++ b/source/yojimbo_base_client.cpp @@ -235,24 +235,32 @@ namespace yojimbo bool BaseClient::CanSendMessage( int channelIndex ) const { yojimbo_assert( m_connection ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_connection->CanSendMessage( channelIndex ); } bool BaseClient::HasMessagesToSend( int channelIndex ) const { yojimbo_assert( m_connection ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_connection->HasMessagesToSend( channelIndex ); } void BaseClient::SendMessage( int channelIndex, Message * message ) { yojimbo_assert( m_connection ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); m_connection->SendMessage( channelIndex, message, GetContext() ); } Message * BaseClient::ReceiveMessage( int channelIndex ) { yojimbo_assert( m_connection ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_connection->ReceiveMessage( channelIndex ); } @@ -273,6 +281,10 @@ namespace yojimbo info.numPacketsReceived = counters[RELIABLE_ENDPOINT_COUNTER_NUM_PACKETS_RECEIVED]; info.numPacketsAcked = counters[RELIABLE_ENDPOINT_COUNTER_NUM_PACKETS_ACKED]; info.RTT = reliable_endpoint_rtt( m_endpoint ); + info.minRTT = reliable_endpoint_rtt_min( m_endpoint ); + info.maxRTT = reliable_endpoint_rtt_min( m_endpoint ); + info.averageRTT = reliable_endpoint_rtt_avg( m_endpoint ); + info.jitter = reliable_endpoint_jitter( m_endpoint ); info.packetLoss = reliable_endpoint_packet_loss( m_endpoint ); reliable_endpoint_bandwidth( m_endpoint, &info.sentBandwidth, &info.receivedBandwidth, &info.ackedBandwidth ); } diff --git a/source/yojimbo_base_server.cpp b/source/yojimbo_base_server.cpp index d60c4403..7f31d1c2 100644 --- a/source/yojimbo_base_server.cpp +++ b/source/yojimbo_base_server.cpp @@ -45,24 +45,33 @@ namespace yojimbo void BaseServer::Start( int maxClients ) { + yojimbo_assert( maxClients <= MaxClients ); + Stop(); + m_running = true; m_maxClients = maxClients; + yojimbo_assert( !m_globalMemory ); yojimbo_assert( !m_globalAllocator ); m_globalMemory = (uint8_t*) YOJIMBO_ALLOCATE( *m_allocator, m_config.serverGlobalMemory ); + m_globalAllocator = m_adapter->CreateAllocator( *m_allocator, m_globalMemory, m_config.serverGlobalMemory ); yojimbo_assert( m_globalAllocator ); + if ( m_config.networkSimulator ) { m_networkSimulator = YOJIMBO_NEW( *m_globalAllocator, NetworkSimulator, *m_globalAllocator, m_config.maxSimulatorPackets, m_time ); } + for ( int i = 0; i < m_maxClients; ++i ) { yojimbo_assert( !m_clientMemory[i] ); yojimbo_assert( !m_clientAllocator[i] ); m_clientMemory[i] = (uint8_t*) YOJIMBO_ALLOCATE( *m_allocator, m_config.serverPerClientMemory ); + yojimbo_assert( m_clientMemory[i] ); + m_clientAllocator[i] = m_adapter->CreateAllocator( *m_allocator, m_clientMemory[i], m_config.serverPerClientMemory ); yojimbo_assert( m_clientAllocator[i] ); @@ -104,6 +113,8 @@ namespace yojimbo yojimbo_assert( m_globalMemory ); yojimbo_assert( m_globalAllocator ); YOJIMBO_DELETE( *m_globalAllocator, NetworkSimulator, m_networkSimulator ); + yojimbo_assert( m_maxClients > 0 ); + yojimbo_assert( m_maxClients <= MaxClients ); for ( int i = 0; i < m_maxClients; ++i ) { yojimbo_assert( m_clientMemory[i] ); @@ -119,6 +130,14 @@ namespace yojimbo YOJIMBO_DELETE( *m_allocator, Allocator, m_globalAllocator ); YOJIMBO_FREE( *m_allocator, m_globalMemory ); } + for ( int i = 0; i < MaxClients; ++i ) + { + m_clientMemory[i] = NULL; + m_clientAllocator[i] = NULL; + m_clientMessageFactory[i] = NULL; + m_clientConnection[i] = NULL; + m_clientEndpoint[i] = NULL; + } m_running = false; m_maxClients = 0; m_packetBuffer = NULL; @@ -223,6 +242,8 @@ namespace yojimbo { yojimbo_assert( clientIndex >= 0 ); yojimbo_assert( clientIndex < m_maxClients ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); yojimbo_assert( m_clientConnection[clientIndex] ); return m_clientConnection[clientIndex]->CanSendMessage( channelIndex ); } @@ -232,6 +253,8 @@ namespace yojimbo yojimbo_assert( clientIndex >= 0 ); yojimbo_assert( clientIndex < m_maxClients ); yojimbo_assert( m_clientConnection[clientIndex] ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_clientConnection[clientIndex]->HasMessagesToSend( channelIndex ); } @@ -240,6 +263,8 @@ namespace yojimbo yojimbo_assert( clientIndex >= 0 ); yojimbo_assert( clientIndex < m_maxClients ); yojimbo_assert( m_clientConnection[clientIndex] ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_clientConnection[clientIndex]->SendMessage( channelIndex, message, GetContext() ); } @@ -248,6 +273,8 @@ namespace yojimbo yojimbo_assert( clientIndex >= 0 ); yojimbo_assert( clientIndex < m_maxClients ); yojimbo_assert( m_clientConnection[clientIndex] ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_clientConnection[clientIndex]->ReceiveMessage( channelIndex ); } diff --git a/source/yojimbo_server.cpp b/source/yojimbo_server.cpp index 470461b4..dd16ffeb 100644 --- a/source/yojimbo_server.cpp +++ b/source/yojimbo_server.cpp @@ -50,8 +50,7 @@ namespace yojimbo void Server::Start( int maxClients ) { - if ( IsRunning() ) - Stop(); + yojimbo_assert( maxClients <= MaxClients ); BaseServer::Start( maxClients ); diff --git a/test.cpp b/test.cpp index 480b27c8..ae3a1301 100644 --- a/test.cpp +++ b/test.cpp @@ -618,6 +618,8 @@ void PumpConnectionUpdate( ConnectionConfig & connectionConfig, double & time, C receiverSequence++; } +const int ReliableChannel = 0; + void test_connection_reliable_ordered_messages() { TestMessageFactory messageFactory( GetDefaultAllocator() ); @@ -636,7 +638,7 @@ void test_connection_reliable_ordered_messages() TestMessage * message = (TestMessage*) messageFactory.CreateMessage( TEST_MESSAGE ); check( message ); message->sequence = i; - sender.SendMessage( 0, message ); + sender.SendMessage( ReliableChannel, message ); } const int SenderPort = 10000; @@ -658,7 +660,7 @@ void test_connection_reliable_ordered_messages() while ( true ) { - Message * message = receiver.ReceiveMessage( 0 ); + Message * message = receiver.ReceiveMessage( ReliableChannel ); if ( !message ) break; @@ -704,7 +706,7 @@ void test_connection_reliable_ordered_blocks() for ( int j = 0; j < blockSize; ++j ) blockData[j] = i + j; message->AttachBlock( messageFactory.GetAllocator(), blockData, blockSize ); - sender.SendMessage( 0, message ); + sender.SendMessage( ReliableChannel, message ); } const int SenderPort = 10000; @@ -726,7 +728,7 @@ void test_connection_reliable_ordered_blocks() while ( true ) { - Message * message = receiver.ReceiveMessage( 0 ); + Message * message = receiver.ReceiveMessage( ReliableChannel ); if ( !message ) break; @@ -784,7 +786,7 @@ void test_connection_reliable_ordered_messages_and_blocks() TestMessage * message = (TestMessage*) messageFactory.CreateMessage( TEST_MESSAGE ); check( message ); message->sequence = i; - sender.SendMessage( 0, message ); + sender.SendMessage( ReliableChannel, message ); } else { @@ -796,7 +798,7 @@ void test_connection_reliable_ordered_messages_and_blocks() for ( int j = 0; j < blockSize; ++j ) blockData[j] = i + j; message->AttachBlock( messageFactory.GetAllocator(), blockData, blockSize ); - sender.SendMessage( 0, message ); + sender.SendMessage( ReliableChannel, message ); } } @@ -819,7 +821,7 @@ void test_connection_reliable_ordered_messages_and_blocks() while ( true ) { - Message * message = receiver.ReceiveMessage( 0 ); + Message * message = receiver.ReceiveMessage( ReliableChannel ); if ( !message ) break; @@ -1179,7 +1181,7 @@ void PumpClientServerUpdate( double & time, Client ** client, int numClients, Se yojimbo_sleep( 0.0f ); } -void SendClientToServerMessages( Client & client, int numMessagesToSend, int channelIndex = 0 ) +void SendClientToServerMessages( Client & client, int numMessagesToSend, int channelIndex = ReliableChannel ) { for ( int i = 0; i < numMessagesToSend; ++i ) { @@ -1209,7 +1211,7 @@ void SendClientToServerMessages( Client & client, int numMessagesToSend, int cha } } -void SendServerToClientMessages( Server & server, int clientIndex, int numMessagesToSend, int channelIndex = 0 ) +void SendServerToClientMessages( Server & server, int clientIndex, int numMessagesToSend, int channelIndex = ReliableChannel ) { for ( int i = 0; i < numMessagesToSend; ++i ) { @@ -1239,11 +1241,11 @@ void SendServerToClientMessages( Server & server, int clientIndex, int numMessag } } -void ProcessServerToClientMessages( Client & client, int & numMessagesReceivedFromServer ) +void ProcessServerToClientMessages( Client & client, int & numMessagesReceivedFromServer, int channelIndex = ReliableChannel ) { while ( true ) { - Message * message = client.ReceiveMessage( 0 ); + Message * message = client.ReceiveMessage( channelIndex ); if ( !message ) break; @@ -1283,11 +1285,11 @@ void ProcessServerToClientMessages( Client & client, int & numMessagesReceivedFr } } -void ProcessClientToServerMessages( Server & server, int clientIndex, int & numMessagesReceivedFromClient ) +void ProcessClientToServerMessages( Server & server, int clientIndex, int & numMessagesReceivedFromClient, int channelIndex = ReliableChannel ) { while ( true ) { - Message * message = server.ReceiveMessage( clientIndex, 0 ); + Message * message = server.ReceiveMessage( clientIndex, channelIndex ); if ( !message ) break; @@ -1511,19 +1513,19 @@ void test_client_server_start_stop_restart() Server server( GetDefaultAllocator(), privateKey, serverAddress, config, adapter, time ); - server.Start( MaxClients ); - server.SetLatency( 250 ); server.SetJitter( 100 ); server.SetPacketLoss( 25 ); server.SetDuplicates( 25 ); - int numClients[] = { 3, 5, 1 }; + int numClients[] = { 3, 5, 1, 32, 5 }; const int NumIterations = sizeof( numClients ) / sizeof( int ); for ( int iteration = 0; iteration < NumIterations; ++iteration ) { + yojimbo_assert( numClients[iteration] < MaxClients ); + server.Start( numClients[iteration] ); Client * clients[MaxClients];