Code reformat and CLIENT SIDE PREDICTION!!

main
Cameron Murphy Reikes 2 years ago
parent 6c0211436e
commit e972537e89

@ -0,0 +1,4 @@
BasedOnStyle: LLVM
ColumnLimit: 0
SpacesInParentheses: false
BreakBeforeBraces: Allman

@ -30,11 +30,6 @@ typedef struct Command
// thread local variables so debug drawing in server thread
// doesn't fuck up main thread
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
#define THREADLOCAL __declspec(thread)
#else
#define THREADLOCAL __thread
#endif
static THREADLOCAL Command commands[MAX_COMMANDS] = {0};
static THREADLOCAL int command_i = 0;

File diff suppressed because it is too large Load Diff

773
main.c

File diff suppressed because it is too large Load Diff

@ -1,34 +1,41 @@
#pragma once
#include <stdbool.h>
#ifndef QUEUE_ASSERT
void __assert(bool cond, const char* file, int line, const char* cond_string);
void __assert(bool cond, const char *file, int line, const char *cond_string);
#define QUEUE_ASSERT(condition) __assert(condition, __FILE__, __LINE__, #condition)
#endif
typedef struct QueueElementHeader {
typedef struct QueueElementHeader
{
bool exists;
struct QueueElementHeader* next;
struct QueueElementHeader *next;
char data[];
} QueueElementHeader;
typedef struct Queue {
char* data;
typedef struct Queue
{
char *data;
size_t data_length; // must be a multiple of sizeof(QueueElementHeader) + element_size
size_t element_size;
QueueElementHeader* next;
QueueElementHeader *next;
} Queue;
#define QUEUE_SIZE_FOR_ELEMENTS(element_size, max_elements) ((sizeof(QueueElementHeader) + element_size) * max_elements)
void queue_init(Queue* q, size_t element_size, char* data, size_t data_length);
void queue_clear(Queue* q);
void* queue_push_element(Queue* q);
size_t queue_num_elements(Queue* q);
void* queue_pop_element(Queue* q);
// oldest to newest
#define QUEUE_ITER(q_ptr, cur_header) for (QueueElementHeader *cur_header = (q_ptr)->next; cur_header != NULL; cur_header = cur_header->next)
void queue_init(Queue *q, size_t element_size, char *data, size_t data_length);
void queue_clear(Queue *q);
void *queue_push_element(Queue *q);
size_t queue_num_elements(Queue *q);
void *queue_pop_element(Queue *q);
void *queue_most_recent_element(Queue *q);
#ifdef QUEUE_IMPL
void queue_init(Queue* q, size_t element_size, char* data, size_t data_length)
void queue_init(Queue *q, size_t element_size, char *data, size_t data_length)
{
q->data = data;
q->data_length = data_length;
@ -36,7 +43,7 @@ void queue_init(Queue* q, size_t element_size, char* data, size_t data_length)
QUEUE_ASSERT(data_length % (sizeof(QueueElementHeader) + element_size) == 0);
}
void queue_clear(Queue* q)
void queue_clear(Queue *q)
{
QUEUE_ASSERT(q->data != NULL);
for (size_t i = 0; i < q->data_length; i++)
@ -46,14 +53,14 @@ void queue_clear(Queue* q)
q->next = NULL;
}
#define QUEUE_ELEM_ITER(cur) for(QueueElementHeader *cur = (QueueElementHeader*)q->data; (char*)cur < q->data + q->data_length; (char*)cur += (sizeof(QueueElementHeader) + q->element_size))
#define QUEUE_ELEM_ITER(cur) for (QueueElementHeader *cur = (QueueElementHeader *)q->data; (char *)cur < q->data + q->data_length; (char *)cur += (sizeof(QueueElementHeader) + q->element_size))
// you push an element, get the return value, cast it to your type, and fill it with data. It's that easy!
// if it's null the queue is out of space
void* queue_push_element(Queue* q)
void *queue_push_element(Queue *q)
{
QUEUE_ASSERT(q->data != NULL);
QueueElementHeader* to_return = NULL;
QueueElementHeader *to_return = NULL;
QUEUE_ELEM_ITER(cur)
{
if (!cur->exists)
@ -68,7 +75,8 @@ void* queue_push_element(Queue* q)
{
return NULL;
}
else {
else
{
to_return->exists = true;
to_return->next = NULL; // very important.
for (size_t i = 0; i < q->element_size; i++)
@ -77,36 +85,53 @@ void* queue_push_element(Queue* q)
// add to the end of the linked list chain
if (q->next != NULL)
{
QueueElementHeader* cur = q->next;
while (cur->next != NULL) cur = cur->next;
QueueElementHeader *cur = q->next;
while (cur->next != NULL)
cur = cur->next;
cur->next = to_return;
}
else {
else
{
q->next = to_return;
}
return (void*)to_return->data;
return (void *)to_return->data;
}
}
size_t queue_num_elements(Queue* q)
size_t queue_num_elements(Queue *q)
{
QUEUE_ASSERT(q->data != NULL);
size_t to_return = 0;
QUEUE_ELEM_ITER(cur)
if (cur->exists) to_return++;
if (cur->exists)
to_return++;
return to_return;
}
// returns null if the queue is empty
void* queue_pop_element(Queue* q)
void *queue_pop_element(Queue *q)
{
QUEUE_ASSERT(q->data != NULL);
QueueElementHeader* to_return = q->next;
if (q->next != NULL) q->next = q->next->next;
if (to_return != NULL) to_return->exists = false; // jank!
return to_return == NULL ? NULL : (void*)to_return->data;
QueueElementHeader *to_return = q->next;
if (q->next != NULL)
q->next = q->next->next;
if (to_return != NULL)
to_return->exists = false; // jank!
return to_return == NULL ? NULL : (void *)to_return->data;
}
void *queue_most_recent_element(Queue *q)
{
if (q->next == NULL)
return NULL;
else
{
QueueElementHeader *cur = q->next;
while (cur->next != NULL)
cur = cur->next;
return (void *)cur->data;
}
}
#undef QUEUE_ELEM_ITER
#endif

@ -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 <stdio.h>
#include <errno.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);

@ -1,7 +1,9 @@
#pragma once
#include "ipsettings.h"
#define MAX_PLAYERS 16
#define MAX_ENTITIES 1024*25
#define MAX_ENTITIES 1024 * 25
#define BOX_SIZE 0.25f
#define PLAYER_SIZE ((V2){.x = BOX_SIZE, .y = BOX_SIZE})
#define PLAYER_MASS 0.5f
@ -20,11 +22,15 @@
#define MAX_CLIENT_TO_SERVER 1024 * 10 // maximum size of serialized inputs and mic data
#define SUN_RADIUS 10.0f
#define INSTANT_DEATH_DISTANCE_FROM_SUN 2000.0f
#define SUN_POS ((V2){50.0f,0.0f})
#define SUN_POS ((V2){50.0f, 0.0f})
#ifdef NO_GRAVITY
#define SUN_GRAVITY_STRENGTH 0.1f
#else
#define SUN_GRAVITY_STRENGTH (9.0e2f)
#endif
#define SOLAR_ENERGY_PER_SECOND 0.02f
#define DAMAGE_TO_PLAYER_PER_BLOCK 0.1f
#define BATTERY_CAPACITY DAMAGE_TO_PLAYER_PER_BLOCK*0.7f
#define BATTERY_CAPACITY DAMAGE_TO_PLAYER_PER_BLOCK * 0.7f
#define PLAYER_ENERGY_RECHARGE_PER_SECOND 0.1f
#define EXPLOSION_TIME 0.5f
#define EXPLOSION_PUSH_STRENGTH 5.0f
@ -38,15 +44,24 @@
#define VOIP_PACKET_BUFFER_SIZE 15 // audio. Must be bigger than 2
#define VOIP_EXPECTED_FRAME_COUNT 480
#define VOIP_SAMPLE_RATE 48000
#define VOIP_TIME_PER_PACKET 1.0f / ((float)(VOIP_SAMPLE_RATE/VOIP_EXPECTED_FRAME_COUNT)) // in seconds
#define VOIP_TIME_PER_PACKET (1.0f / ((float)(VOIP_SAMPLE_RATE / VOIP_EXPECTED_FRAME_COUNT))) // in seconds
#define VOIP_PACKET_MAX_SIZE 4000
#define VOIP_DISTANCE_WHEN_CANT_HEAR (VISION_RADIUS*0.8f)
#define VOIP_DISTANCE_WHEN_CANT_HEAR (VISION_RADIUS * 0.8f)
#define MAX_REPREDICTION_TIME (TIMESTEP * 50.0f)
#define TIME_BETWEEN_SEND_GAMESTATE (1.0f / 20.0f)
#define TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f)
#define TIMESTEP (1.0f / 60.0f) // server required to simulate at this, defines what tick the game is on
#define SERVER_PORT 2551
#define INPUT_BUFFER 6
#define LOCAL_INPUT_QUEUE_MAX 90 // please god let you not have more than 90 frames of game latency
#define INPUT_QUEUE_MAX 15
// cross platform threadlocal variables
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
#define THREADLOCAL __declspec(thread)
#else
#define THREADLOCAL __thread
#endif
// must make this header and set the target address, just #define SERVER_ADDRESS "127.0.0.1"
#include "ipsettings.h" // don't leak IP!
@ -85,8 +100,8 @@ typedef void cpBody;
typedef void cpShape;
#endif
#include <stdbool.h>
#include "queue.h"
#include <stdbool.h>
#ifndef OPUS_TYPES_H
typedef int opus_int32;
@ -103,9 +118,11 @@ typedef int opus_int32;
typedef sgp_vec2 V2;
typedef sgp_point P2;
#define Log(...){ \
#define Log(...) \
{ \
fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); \
fprintf(stdout, __VA_ARGS__);}
fprintf(stdout, __VA_ARGS__); \
}
enum BoxType
{
@ -155,7 +172,6 @@ static bool entityids_same(EntityID a, EntityID b)
typedef struct InputFrame
{
uint64_t tick;
size_t id; // each input has unique, incrementing, I.D, so server doesn't double process inputs. Inputs to server should be ordered from 0-max like biggest id-smallest. This is done so if packet loss server still processes input
V2 movement;
int take_over_squad; // -1 means not taking over any squad
@ -164,10 +180,8 @@ typedef struct InputFrame
EntityID invite_this_player; // null means inviting nobody! @Robust make it so just sends interact pos input, and server processes who to invite. This depends on client side prediction + proper input processing at the right tick.
bool seat_action;
EntityID seat_to_inhabit;
V2 hand_pos; // local to player transationally but not rotationally unless field below is not null, then it's local to that grid
V2 hand_pos; // local to player transationally but not rotationally
// @BeforeShip bounds check on the hand_pos so that players can't reach across the entire map
EntityID grid_hand_pos_local_to; // when not null, hand_pos is local to this grid. this prevents bug where at high speeds the built block is in the wrong place on the selected grid
bool dobuild;
enum BoxType build_type;
@ -183,8 +197,8 @@ typedef struct Entity
bool no_save_to_disk; // stuff generated later on, like player's bodies or space stations that respawn.
float damage; // used by box and player
cpBody* body; // used by grid, player, and box
cpShape* shape; // must be a box so shape_size can be set appropriately, and serialized
cpBody *body; // used by grid, player, and box
cpShape *shape; // must be a box so shape_size can be set appropriately, and serialized
// for serializing the shape
// @Robust remove shape_parent_entity from this struct, use the shape's body to figure out
@ -238,9 +252,9 @@ typedef struct Player
// gotta update the serialization functions when this changes
typedef struct GameState
{
cpSpace* space;
cpSpace *space;
double time;
double time; // @Robust separate tick integer not prone to precision issues
V2 goldpos;
@ -252,16 +266,18 @@ typedef struct GameState
// ent:ity pointers can't move around because of how the physics engine handles user data.
// if you really need this, potentially refactor to store entity IDs instead of pointers
// in the shapes and bodies of chipmunk. Would require editing the library I think
Entity* entities;
Entity *entities;
unsigned int max_entities; // maximum number of entities possible in the entities list
unsigned int cur_next_entity; // next entity to pass on request of a new entity if the free list is empty
EntityID free_list;
} GameState;
#define PLAYERS_ITER(players, cur) for(Player * cur = players; cur < players+MAX_PLAYERS; cur++) if(cur->connected)
#define PLAYERS_ITER(players, cur) \
for (Player *cur = players; cur < players + MAX_PLAYERS; cur++) \
if (cur->connected)
#define PI 3.14159f
#define TAU (PI*2.0f)
#define TAU (PI * 2.0f)
// returns in radians
static float rotangle(enum CompassRotation rot)
@ -287,82 +303,85 @@ static float rotangle(enum CompassRotation rot)
}
}
typedef struct OpusPacket {
typedef struct OpusPacket
{
opus_int32 length;
char data[VOIP_PACKET_MAX_SIZE];
} OpusPacket;
typedef struct ServerToClient
{
struct GameState* cur_gs;
Queue* playback_buffer;
struct GameState *cur_gs;
Queue *audio_playback_buffer;
int your_player;
} ServerToClient;
typedef struct ClientToServer
{
Queue* mic_data; // on serialize, flushes this of packets. On deserialize, fills it
InputFrame inputs[INPUT_BUFFER];
Queue *mic_data; // on serialize, flushes this of packets. On deserialize, fills it
Queue *input_data; // does not flush on serialize! must be in order of tick
} ClientToServer;
#define DeferLoop(start, end) \
for (int _i_ = ((start), 0); _i_ == 0; _i_ += 1, (end))
// server
void server(void* info); // data parameter required from thread api...
void server(void *info); // data parameter required from thread api...
// gamestate
EntityID create_spacestation(GameState* gs);
void initialize(struct GameState* gs, void* entity_arena, size_t entity_arena_size);
void destroy(struct GameState* gs);
void process(struct GameState* gs, float dt); // does in place
Entity* closest_to_point_in_radius(struct GameState* gs, V2 point, float radius);
uint64_t tick(struct GameState* gs);
EntityID create_spacestation(GameState *gs);
void initialize(struct GameState *gs, void *entity_arena, size_t entity_arena_size);
void destroy(struct GameState *gs);
void process_fixed_timestep(GameState *gs);
void process(struct GameState *gs, float dt); // does in place
Entity *closest_to_point_in_radius(struct GameState *gs, V2 point, float radius);
uint64_t tick(struct GameState *gs);
// all of these return if successful or not
bool server_to_client_serialize(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t max_len, Entity* for_this_player, bool to_disk);
bool server_to_client_deserialize(struct ServerToClient* msg, char* bytes, size_t max_len, bool from_disk);
bool client_to_server_deserialize(GameState* gs, struct ClientToServer* msg, char* bytes, size_t max_len);
bool client_to_server_serialize(GameState* gs, struct ClientToServer* msg, char* bytes, size_t* out_len, size_t max_len);
bool server_to_client_serialize(struct ServerToClient *msg, char *bytes, size_t *out_len, size_t max_len, Entity *for_this_player, bool to_disk);
bool server_to_client_deserialize(struct ServerToClient *msg, char *bytes, size_t max_len, bool from_disk);
bool client_to_server_deserialize(GameState *gs, struct ClientToServer *msg, char *bytes, size_t max_len);
bool client_to_server_serialize(GameState *gs, struct ClientToServer *msg, char *bytes, size_t *out_len, size_t max_len);
// entities
Entity* get_entity(struct GameState* gs, EntityID id);
Entity* new_entity(struct GameState* gs);
EntityID get_id(struct GameState* gs, Entity* e);
V2 entity_pos(Entity* e);
void entity_set_rotation(Entity* e, float rot);
void entity_set_pos(Entity* e, V2 pos);
float entity_rotation(Entity* e);
void entity_ensure_in_orbit(Entity* e);
void entity_destroy(GameState* gs, Entity* e);
Entity *get_entity(struct GameState *gs, EntityID id);
Entity *new_entity(struct GameState *gs);
EntityID get_id(struct GameState *gs, Entity *e);
V2 entity_pos(Entity *e);
void entity_set_rotation(Entity *e, float rot);
void entity_set_pos(Entity *e, V2 pos);
float entity_rotation(Entity *e);
void entity_ensure_in_orbit(Entity *e);
void entity_destroy(GameState *gs, Entity *e);
#define BOX_CHAIN_ITER(gs, cur, starting_box) for (Entity *cur = get_entity(gs, starting_box); cur != NULL; cur = get_entity(gs, cur->next_box))
#define BOXES_ITER(gs, cur, grid_entity_ptr) BOX_CHAIN_ITER(gs, cur, (grid_entity_ptr)->boxes)
// grid
void grid_create(struct GameState* gs, Entity* e);
void box_create(struct GameState* gs, Entity* new_box, Entity* grid, V2 pos);
V2 grid_com(Entity* grid);
V2 grid_vel(Entity* grid);
V2 box_vel(Entity* box);
V2 grid_local_to_world(Entity* grid, V2 local);
V2 grid_world_to_local(Entity* grid, V2 world);
V2 grid_snapped_box_pos(Entity* grid, V2 world); // returns the snapped pos in world coords
float entity_angular_velocity(Entity* grid);
V2 entity_shape_pos(Entity* box);
float box_rotation(Entity* box);
void grid_create(struct GameState *gs, Entity *e);
void box_create(struct GameState *gs, Entity *new_box, Entity *grid, V2 pos);
V2 grid_com(Entity *grid);
V2 grid_vel(Entity *grid);
V2 box_vel(Entity *box);
V2 grid_local_to_world(Entity *grid, V2 local);
V2 grid_world_to_local(Entity *grid, V2 world);
V2 grid_snapped_box_pos(Entity *grid, V2 world); // returns the snapped pos in world coords
float entity_angular_velocity(Entity *grid);
V2 entity_shape_pos(Entity *box);
float box_rotation(Entity *box);
// thruster
V2 box_facing_vector(Entity* box);
V2 thruster_force(Entity* box);
V2 box_facing_vector(Entity *box);
V2 thruster_force(Entity *box);
// debug draw
void dbg_drawall();
void dbg_line(V2 from, V2 to);
void dbg_rect(V2 center);
typedef struct ServerThreadInfo {
typedef struct ServerThreadInfo
{
ma_mutex info_mutex;
const char* world_save;
const char *world_save;
bool should_quit;
} ServerThreadInfo;
// all the math is static so that it can be defined in each compilation unit its included in
@ -374,8 +393,7 @@ typedef struct AABB
static AABB centered_at(V2 point, V2 size)
{
return (AABB)
{
return (AABB){
.x = point.x - size.x / 2.0f,
.y = point.y - size.y / 2.0f,
.width = size.x,
@ -390,7 +408,7 @@ static bool has_point(AABB aabb, V2 point)
static V2 V2add(V2 a, V2 b)
{
return (V2) {
return (V2){
.x = a.x + b.x,
.y = a.y + b.y,
};
@ -398,7 +416,7 @@ static V2 V2add(V2 a, V2 b)
static V2 V2scale(V2 a, float f)
{
return (V2) {
return (V2){
.x = a.x * f,
.y = a.y * f,
};
@ -419,7 +437,6 @@ static V2 V2normalize(V2 v)
return V2scale(v, 1.0f / V2length(v));
}
static float V2dot(V2 a, V2 b)
{
return a.x * b.x + a.y * b.y;
@ -438,7 +455,7 @@ static V2 V2project(V2 vec, V2 onto)
static V2 V2rotate(V2 vec, float theta)
{
return (V2) {
return (V2){
.x = vec.x * cosf(theta) - vec.y * sinf(theta),
.y = vec.x * sinf(theta) + vec.y * cosf(theta),
};
@ -452,7 +469,7 @@ static float V2angle(V2 vec)
static V2 V2sub(V2 a, V2 b)
{
return (V2) {
return (V2){
.x = a.x - b.x,
.y = a.y - b.y,
};
@ -497,7 +514,8 @@ static float lerp(float a, float b, float f)
return a * (1.0f - f) + (b * f);
}
static float lerp_angle(float p_from, float p_to, float p_weight) {
static float lerp_angle(float p_from, float p_to, float p_weight)
{
float difference = fmodf(p_to - p_from, (float)TAU);
float distance = fmodf(2.0f * difference, (float)TAU) - difference;
return p_from + distance * p_weight;
@ -505,12 +523,12 @@ static float lerp_angle(float p_from, float p_to, float p_weight) {
static V2 V2floor(V2 p)
{
return (V2) { floorf(p.x), floorf(p.y) };
return (V2){floorf(p.x), floorf(p.y)};
}
static V2 V2fract(V2 p)
{
return (V2) { fract(p.x), fract(p.y) };
return (V2){fract(p.x), fract(p.y)};
}
/*
float noise(V2 p)
@ -548,7 +566,7 @@ float fbm(V2 p)
static V2 V2lerp(V2 a, V2 b, float factor)
{
V2 to_return = { 0 };
V2 to_return = {0};
to_return.x = lerp(a.x, b.x, factor);
to_return.y = lerp(a.y, b.y, factor);
@ -571,7 +589,7 @@ typedef struct Color
static Color colhex(int r, int g, int b)
{
return (Color) {
return (Color){
.r = (float)r / 255.0f,
.g = (float)g / 255.0f,
.b = (float)b / 255.0f,
@ -590,7 +608,7 @@ static Color colhexcode(int hexcode)
static Color Collerp(Color a, Color b, float factor)
{
Color to_return = { 0 };
Color to_return = {0};
to_return.r = lerp(a.r, b.r, factor);
to_return.g = lerp(a.g, b.g, factor);
to_return.b = lerp(a.b, b.b, factor);

Loading…
Cancel
Save