@ -1,12 +1,12 @@
# include "sokol_time.h"
# include <chipmunk.h> // initializing bodies
# include "types.h"
# include "sokol_time.h"
# include <enet/enet.h>
# include < stdi o.h>
# include < errn o.h>
# include <inttypes.h> // int64 printing
# include <stdio.h>
# include <stdlib.h>
# include <string.h> // error string
# include <errno.h>
# include "minilzo.h"
@ -23,7 +23,7 @@
# ifdef PROFILING
# define SPALL_IMPLEMENTATION
# pragma warning(disable :4996) // spall uses fopen
# pragma warning(disable : 4996) // spall uses fopen
# include "spall.h"
# define WIN32_LEAN_AND_MEAN
@ -31,9 +31,11 @@
# define NOMINMAX
# include <Windows.h>
// This is slow, if you can use RDTSC and set the multiplier in SpallInit, you'll have far better timing accuracy
double get_time_in_micros ( ) {
double get_time_in_micros ( )
{
static double invfreq ;
if ( ! invfreq ) {
if ( ! invfreq )
{
LARGE_INTEGER frequency ;
QueryPerformanceFrequency ( & frequency ) ;
invfreq = 1000000.0 / frequency . QuadPart ;
@ -54,14 +56,14 @@ static SpallBuffer spall_buffer;
# endif
// started in a thread from host
void server ( void * info_raw )
void server ( void * info_raw )
{
ServerThreadInfo * info = ( ServerThreadInfo * ) info_raw ;
const char * world_save_name = info - > world_save ;
ServerThreadInfo * info = ( ServerThreadInfo * ) info_raw ;
const char * world_save_name = info - > world_save ;
# ifdef PROFILING
# define BUFFER_SIZE (1 * 1024 * 1024)
spall_ctx = SpallInit ( " server.spall " , 1 ) ;
unsigned char * buffer = malloc ( BUFFER_SIZE ) ;
unsigned char * buffer = malloc ( BUFFER_SIZE ) ;
spall_buffer = ( SpallBuffer ) {
. length = BUFFER_SIZE ,
. data = buffer ,
@ -70,18 +72,25 @@ void server(void* info_raw)
# endif
struct GameState gs = { 0 } ;
struct GameState gs = { 0 } ;
size_t entities_size = ( sizeof ( Entity ) * MAX_ENTITIES ) ;
Entity * entity_data = malloc ( entities_size ) ;
Entity * entity_data = malloc ( entities_size ) ;
initialize ( & gs , entity_data , entities_size ) ;
Log ( " Allocated %zu bytes for entities \n " , entities_size ) ;
Queue player_voip_buffers [ MAX_PLAYERS ] = { 0 } ;
// inputs
Queue player_input_queues [ MAX_PLAYERS ] = { 0 } ;
size_t input_queue_data_size = QUEUE_SIZE_FOR_ELEMENTS ( sizeof ( InputFrame ) , INPUT_QUEUE_MAX ) ;
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
queue_init ( & player_input_queues [ i ] , sizeof ( InputFrame ) , calloc ( 1 , input_queue_data_size ) , input_queue_data_size ) ;
// voip
Queue player_voip_buffers [ MAX_PLAYERS ] = { 0 } ;
size_t player_voip_buffer_size = QUEUE_SIZE_FOR_ELEMENTS ( sizeof ( OpusPacket ) , VOIP_PACKET_BUFFER_SIZE ) ;
for ( int i = 0 ; i < MAX_PLAYERS ; i + + ) queue_init ( & player_voip_buffers [ i ] , sizeof ( OpusPacket ) , calloc ( 1 , player_voip_buffer_size ) , player_voip_buffer_size ) ;
OpusEncoder * player_encoders [ MAX_PLAYERS ] = { 0 } ;
OpusDecoder * player_decoders [ MAX_PLAYERS ] = { 0 } ;
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
queue_init ( & player_voip_buffers [ i ] , sizeof ( OpusPacket ) , calloc ( 1 , player_voip_buffer_size ) , player_voip_buffer_size ) ;
OpusEncoder * player_encoders [ MAX_PLAYERS ] = { 0 } ;
OpusDecoder * player_decoders [ MAX_PLAYERS ] = { 0 } ;
// for (int i = 0; i < MAX_PLAYERS; i++)
//{
@ -95,13 +104,13 @@ void server(void* info_raw)
if ( world_save_name ! = NULL )
{
size_t read_game_data_buffer_size = entities_size ;
char * read_game_data = malloc ( read_game_data_buffer_size ) ;
char * read_game_data = malloc ( read_game_data_buffer_size ) ;
FILE * file = NULL ;
fopen_s ( & file , ( const char * ) world_save_name , " rb " ) ;
FILE * file = NULL ;
fopen_s ( & file , ( const char * ) world_save_name , " rb " ) ;
if ( file = = NULL )
{
Log ( " Could not read from data file %s: errno %d \n " , ( const char * ) world_save_name , errno ) ;
Log ( " Could not read from data file %s: errno %d \n " , ( const char * ) world_save_name , errno ) ;
}
else
{
@ -133,19 +142,19 @@ void server(void* info_raw)
// one box policy
if ( false )
{
Entity * grid = new_entity ( & gs ) ;
Entity * grid = new_entity ( & gs ) ;
grid_create ( & gs , grid ) ;
entity_set_pos ( grid , ( V2 ) { - BOX_SIZE * 2 , 0.0f } ) ;
Entity * box = new_entity ( & gs ) ;
box_create ( & gs , box , grid , ( V2 ) { 0 } ) ;
entity_set_pos ( grid , ( V2 ) { - BOX_SIZE * 2 , 0.0f } ) ;
Entity * box = new_entity ( & gs ) ;
box_create ( & gs , box , grid , ( V2 ) { 0 } ) ;
}
// rotation test
if ( false )
{
Entity * grid = new_entity ( & gs ) ;
Entity * grid = new_entity ( & gs ) ;
grid_create ( & gs , grid ) ;
entity_set_pos ( grid , ( V2 ) { - BOX_SIZE * 2 , 0.0f } ) ;
entity_set_pos ( grid , ( V2 ) { - BOX_SIZE * 2 , 0.0f } ) ;
entity_set_rotation ( grid , PI / 1.7f ) ;
cpBodySetVelocity ( grid - > body , cpv ( - 0.1 , 0.0 ) ) ;
cpBodySetAngularVelocity ( grid - > body , 1.0f ) ;
@ -164,7 +173,7 @@ void server(void* info_raw)
}
ENetAddress address ;
ENetHost * enet_host ;
ENetHost * enet_host ;
int sethost = enet_address_set_host_ip ( & address , LOCAL_SERVER_ADDRESS ) ;
if ( sethost ! = 0 )
{
@ -189,14 +198,15 @@ void server(void* info_raw)
uint64_t last_processed_time = stm_now ( ) ;
uint64_t last_saved_world_time = stm_now ( ) ;
uint64_t last_sent_audio_time = stm_now ( ) ;
uint64_t last_sent_gamestate_time = stm_now ( ) ;
float audio_time_to_send = 0.0f ;
float total_time = 0.0f ;
size_t player_to_latest_id_processed [ MAX_PLAYERS ] = { 0 } ;
char * world_save_buffer = malloc ( entities_size ) ;
char * world_save_buffer = malloc ( entities_size ) ;
while ( true )
{
ma_mutex_lock ( & info - > info_mutex ) ;
if ( info - > should_quit ) {
if ( info - > should_quit )
{
ma_mutex_unlock ( & info - > info_mutex ) ;
break ;
}
@ -238,10 +248,9 @@ void server(void* info_raw)
}
else
{
event . peer - > data = ( void * ) player_slot ;
gs . players [ player_slot ] = ( struct Player ) { 0 } ;
event . peer - > data = ( void * ) player_slot ;
gs . players [ player_slot ] = ( struct Player ) { 0 } ;
gs . players [ player_slot ] . connected = true ;
player_to_latest_id_processed [ player_slot ] = 0 ;
int error ;
player_encoders [ player_slot ] = opus_encoder_create ( VOIP_SAMPLE_RATE , 1 , OPUS_APPLICATION_VOIP , & error ) ;
@ -268,62 +277,37 @@ void server(void* info_raw)
{
Log ( " Wtf an empty packet from enet? \n " ) ;
}
else {
else
{
int64_t player_slot = ( int64_t ) event . peer - > data ;
size_t length = event . packet - > dataLength ;
# define VOIP_QUEUE_DECL(queue_name, queue_data_name) Queue queue_name = {0}; char queue_data_name[QUEUE_SIZE_FOR_ELEMENTS(sizeof(OpusPacket), VOIP_PACKET_BUFFER_SIZE)] = {0}; queue_init(&queue_name, sizeof(OpusPacket), queue_data_name, QUEUE_SIZE_FOR_ELEMENTS(sizeof(OpusPacket), VOIP_PACKET_BUFFER_SIZE))
# define VOIP_QUEUE_DECL(queue_name, queue_data_name) \
Queue queue_name = { 0 } ; \
char queue_data_name [ QUEUE_SIZE_FOR_ELEMENTS ( sizeof ( OpusPacket ) , VOIP_PACKET_BUFFER_SIZE ) ] = { 0 } ; \
queue_init ( & queue_name , sizeof ( OpusPacket ) , queue_data_name , QUEUE_SIZE_FOR_ELEMENTS ( sizeof ( OpusPacket ) , VOIP_PACKET_BUFFER_SIZE ) )
VOIP_QUEUE_DECL ( throwaway_buffer , throwaway_buffer_data ) ;
Queue * buffer_to_fill = & player_voip_buffers [ player_slot ] ;
if ( get_entity ( & gs , gs . players [ player_slot ] . entity ) = = NULL ) buffer_to_fill = & throwaway_buffer ;
struct ClientToServer received = { . mic_data = buffer_to_fill } ;
if ( ! client_to_server_deserialize ( & gs , & received , event . packet - > data , event . packet - > dataLength ) )
{
Log ( " Bad packet from client %d \n " , ( int ) player_slot ) ;
}
else
{
size_t latest_id = player_to_latest_id_processed [ player_slot ] ;
Queue * buffer_to_fill = & player_voip_buffers [ player_slot ] ;
if ( get_entity ( & gs , gs . players [ player_slot ] . entity ) = = NULL )
buffer_to_fill = & throwaway_buffer ;
if ( received . inputs [ 0 ] . id > latest_id )
{
for ( int i = INPUT_BUFFER - 1 ; i > = 0 ; i - - )
{
if ( received . inputs [ i ] . tick = = 0 ) // empty input
continue ;
if ( received . inputs [ i ] . id < = latest_id )
continue ; // don't reprocess inputs already processed
InputFrame cur_input = received . inputs [ i ] ;
gs . players [ player_slot ] . input . movement = cur_input . movement ;
gs . players [ player_slot ] . input . hand_pos = cur_input . hand_pos ;
queue_clear ( & player_input_queues [ player_slot ] ) ;
struct ClientToServer received = { . mic_data = buffer_to_fill , . input_data = & player_input_queues [ player_slot ] } ;
char decompressed [ MAX_CLIENT_TO_SERVER ] = { 0 } ;
size_t decompressed_max_len = MAX_CLIENT_TO_SERVER ;
assert ( LZO1X_MEM_DECOMPRESS = = 0 ) ;
// for these "event" inputs, only modify the current input if the event is true.
// while processing the gamestate, will mark it as false once processed. This
// prevents setting the event input to false before it's been processed.
if ( cur_input . take_over_squad > = 0 )
gs . players [ player_slot ] . input . take_over_squad = cur_input . take_over_squad ;
if ( cur_input . accept_cur_squad_invite )
gs . players [ player_slot ] . input . accept_cur_squad_invite = cur_input . accept_cur_squad_invite ;
if ( cur_input . reject_cur_squad_invite )
gs . players [ player_slot ] . input . reject_cur_squad_invite = cur_input . reject_cur_squad_invite ;
if ( cur_input . invite_this_player . generation > 0 )
{
gs . players [ player_slot ] . input . invite_this_player = cur_input . invite_this_player ;
}
if ( cur_input . seat_action )
int return_value = lzo1x_decompress_safe ( event . packet - > data , event . packet - > dataLength , decompressed , & decompressed_max_len , NULL ) ;
if ( return_value = = LZO_E_OK )
{
gs . players [ player_slot ] . input . seat_action = cur_input . seat_action ;
gs . players [ player_slot ] . input . grid_hand_pos_local_to = cur_input . grid_hand_pos_local_to ;
}
if ( cur_input . dobuild )
if ( ! client_to_server_deserialize ( & gs , & received , decompressed , decompressed_max_len ) )
{
gs . players [ player_slot ] . input . grid_hand_pos_local_to = cur_input . grid_hand_pos_local_to ;
gs . players [ player_slot ] . input . dobuild = cur_input . dobuild ;
gs . players [ player_slot ] . input . build_type = cur_input . build_type ;
gs . players [ player_slot ] . input . build_rotation = cur_input . build_rotation ;
}
Log ( " Bad packet from client %d \n " , ( int ) player_slot ) ;
}
player_to_latest_id_processed [ player_slot ] = received . inputs [ 0 ] . id ;
}
else
{
Log ( " Couldn't decompress player packet, error code %d from lzo \n " , return_value ) ;
}
}
/* Clean up the packet now that we're done using it. */
@ -335,7 +319,7 @@ void server(void* info_raw)
{
int player_index = ( int ) ( int64_t ) event . peer - > data ;
Log ( " % " PRId64 " disconnected player index %d. \n " , ( int64_t ) event . peer - > data , player_index ) ;
Entity * player_body = get_entity ( & gs , gs . players [ player_index ] . entity ) ;
Entity * player_body = get_entity ( & gs , gs . players [ player_index ] . entity ) ;
if ( player_body ! = NULL )
{
entity_destroy ( & gs , player_body ) ;
@ -356,7 +340,6 @@ void server(void* info_raw)
break ;
}
}
}
total_time + = ( float ) stm_sec ( stm_diff ( stm_now ( ) , last_processed_time ) ) ;
last_processed_time = stm_now ( ) ;
@ -368,12 +351,24 @@ void server(void* info_raw)
total_time = max_time ;
}
bool processed = false ;
PROFILE_SCOPE ( " World Processing " )
{
while ( total_time > TIMESTEP )
{
processed = true ;
CONNECTED_PEERS ( enet_host , cur )
{
int this_player_index = ( int ) ( int64_t ) cur - > data ;
QUEUE_ITER ( & player_input_queues [ this_player_index ] , cur_header )
{
InputFrame * cur = ( InputFrame * ) cur_header - > data ;
if ( cur - > tick = = tick ( & gs ) )
{
gs . players [ this_player_index ] . input = * cur ;
break ;
}
}
}
process ( & gs , TIMESTEP ) ;
total_time - = TIMESTEP ;
}
@ -381,7 +376,8 @@ void server(void* info_raw)
if ( world_save_name ! = NULL & & ( stm_sec ( stm_diff ( stm_now ( ) , last_saved_world_time ) ) ) > TIME_BETWEEN_WORLD_SAVE )
{
PROFILE_SCOPE ( " Save World " ) {
PROFILE_SCOPE ( " Save World " )
{
last_saved_world_time = stm_now ( ) ;
ServerToClient msg = ( ServerToClient ) {
. cur_gs = & gs ,
@ -389,8 +385,8 @@ void server(void* info_raw)
size_t out_len = 0 ;
if ( server_to_client_serialize ( & msg , world_save_buffer , & out_len , entities_size , NULL , true ) )
{
FILE * save_file = NULL ;
fopen_s ( & save_file , ( const char * ) world_save_name , " wb " ) ;
FILE * save_file = NULL ;
fopen_s ( & save_file , ( const char * ) world_save_name , " wb " ) ;
if ( save_file = = NULL )
{
Log ( " Could not open save file: errno %d \n " , errno ) ;
@ -404,7 +400,7 @@ void server(void* info_raw)
}
else
{
Log ( " Saved game world to %s \n " , ( const char * ) world_save_name ) ;
Log ( " Saved game world to %s \n " , ( const char * ) world_save_name ) ;
}
fclose ( save_file ) ;
}
@ -416,10 +412,12 @@ void server(void* info_raw)
}
}
if ( processed )
if ( stm_sec ( stm_diff ( stm_now ( ) , last_sent_gamestate_time ) ) > TIME_BETWEEN_SEND_GAMESTATE )
{
PROFILE_SCOPE ( " send_data " ) {
static char lzo_working_mem [ LZO1X_1_MEM_COMPRESS ] = { 0 } ;
last_sent_gamestate_time = stm_now ( ) ;
PROFILE_SCOPE ( " send_data " )
{
static char lzo_working_mem [ LZO1X_1_MEM_COMPRESS ] = { 0 } ;
audio_time_to_send + = ( float ) stm_sec ( stm_diff ( stm_now ( ) , last_sent_audio_time ) ) ;
last_sent_audio_time = stm_now ( ) ;
@ -432,7 +430,7 @@ void server(void* info_raw)
num_audio_packets = MAX_AUDIO_PACKETS_TO_SEND ;
}
opus_int16 decoded_audio_packets [ MAX_PLAYERS ] [ MAX_AUDIO_PACKETS_TO_SEND ] [ VOIP_EXPECTED_FRAME_COUNT ] = { 0 } ;
opus_int16 decoded_audio_packets [ MAX_PLAYERS ] [ MAX_AUDIO_PACKETS_TO_SEND ] [ VOIP_EXPECTED_FRAME_COUNT ] = { 0 } ;
audio_time_to_send - = num_audio_packets * VOIP_TIME_PER_PACKET ;
@ -442,8 +440,8 @@ void server(void* info_raw)
int this_player_index = ( int ) ( int64_t ) cur - > data ;
for ( int packet_i = 0 ; packet_i < num_audio_packets ; packet_i + + )
{
opus_int16 * to_dump_to = decoded_audio_packets [ this_player_index ] [ packet_i ] ;
OpusPacket * cur_packet = ( OpusPacket * ) queue_pop_element ( & player_voip_buffers [ this_player_index ] ) ;
opus_int16 * to_dump_to = decoded_audio_packets [ this_player_index ] [ packet_i ] ;
OpusPacket * cur_packet = ( OpusPacket * ) queue_pop_element ( & player_voip_buffers [ this_player_index ] ) ;
if ( cur_packet = = NULL )
opus_decode ( player_decoders [ this_player_index ] , NULL , 0 , to_dump_to , VOIP_EXPECTED_FRAME_COUNT , 0 ) ;
else
@ -455,25 +453,25 @@ void server(void* info_raw)
CONNECTED_PEERS ( enet_host , cur )
{
int this_player_index = ( int ) ( int64_t ) cur - > data ;
Entity * this_player_entity = get_entity ( & gs , gs . players [ this_player_index ] . entity ) ;
Entity * this_player_entity = get_entity ( & gs , gs . players [ this_player_index ] . entity ) ;
if ( this_player_entity = = NULL )
continue ;
// @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again
char * bytes_buffer = malloc ( sizeof * bytes_buffer * MAX_SERVER_TO_CLIENT ) ;
char * compressed_buffer = malloc ( sizeof * compressed_buffer * MAX_SERVER_TO_CLIENT ) ;
char * bytes_buffer = malloc ( sizeof * bytes_buffer * MAX_SERVER_TO_CLIENT ) ;
char * compressed_buffer = malloc ( sizeof * compressed_buffer * MAX_SERVER_TO_CLIENT ) ;
// mix audio to be sent
VOIP_QUEUE_DECL ( buffer_to_play , buffer_to_play_data ) ;
{
for ( int packet_i = 0 ; packet_i < num_audio_packets ; packet_i + + )
{
opus_int16 to_send_to_cur [ VOIP_EXPECTED_FRAME_COUNT ] = { 0 } ; // mix what other players said into this buffer
opus_int16 to_send_to_cur [ VOIP_EXPECTED_FRAME_COUNT ] = { 0 } ; // mix what other players said into this buffer
CONNECTED_PEERS ( enet_host , other_player )
{
if ( other_player ! = cur )
{
int other_player_index = ( int ) ( int64_t ) other_player - > data ;
Entity * other_player_entity = get_entity ( & gs , gs . players [ other_player_index ] . entity ) ;
Entity * other_player_entity = get_entity ( & gs , gs . players [ other_player_index ] . entity ) ;
if ( other_player_entity ! = NULL )
{
float dist = V2dist ( entity_pos ( this_player_entity ) , entity_pos ( other_player_entity ) ) ;
@ -488,13 +486,14 @@ void server(void* info_raw)
}
}
}
OpusPacket * this_packet = ( OpusPacket * ) queue_push_element ( & buffer_to_play ) ;
OpusPacket * this_packet = ( OpusPacket * ) queue_push_element ( & buffer_to_play ) ;
opus_int32 ret = opus_encode ( player_encoders [ this_player_index ] , to_send_to_cur , VOIP_EXPECTED_FRAME_COUNT , this_packet - > data , VOIP_PACKET_MAX_SIZE ) ;
if ( ret < 0 )
{
Log ( " Failed to encode audio packet for player %d: opus error code %d \n " , this_player_index , ret ) ;
}
else {
else
{
this_packet - > length = ret ;
}
}
@ -502,7 +501,7 @@ void server(void* info_raw)
ServerToClient to_send = ( ServerToClient ) {
. cur_gs = & gs ,
. your_player = this_player_index ,
. playback_buffer = & buffer_to_play ,
. audio_ playback_buffer = & buffer_to_play ,
} ;
size_t len = 0 ;
@ -515,12 +514,12 @@ void server(void* info_raw)
}
size_t compressed_len = 0 ;
lzo1x_1_compress ( bytes_buffer , len , compressed_buffer , & compressed_len , ( void * ) lzo_working_mem ) ;
lzo1x_1_compress ( bytes_buffer , len , compressed_buffer , & compressed_len , ( void * ) lzo_working_mem ) ;
# ifdef LOG_GAMESTATE_SIZE
Log ( " Size of gamestate packet before comrpession: %zu | After: %zu \n " , len , compressed_len ) ;
# endif
ENetPacket * gamestate_packet = enet_packet_create ( ( void * ) compressed_buffer , compressed_len , ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT ) ;
ENetPacket * gamestate_packet = enet_packet_create ( ( void * ) compressed_buffer , compressed_len , ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT ) ;
int err = enet_peer_send ( cur , 0 , gamestate_packet ) ;
if ( err < 0 )
{
@ -536,7 +535,6 @@ void server(void* info_raw)
free ( compressed_buffer ) ;
}
}
}
}
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
@ -546,7 +544,10 @@ void server(void* info_raw)
if ( player_decoders [ i ] ! = NULL )
opus_decoder_destroy ( player_decoders [ i ] ) ;
}
for ( int i = 0 ; i < MAX_PLAYERS ; i + + ) free ( player_voip_buffers [ i ] . data ) ;
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
free ( player_voip_buffers [ i ] . data ) ;
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
free ( player_input_queues [ i ] . data ) ;
free ( world_save_buffer ) ;
destroy ( & gs ) ;
free ( entity_data ) ;