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 // thread local variables so debug drawing in server thread
// doesn't fuck up main 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 Command commands[MAX_COMMANDS] = {0};
static THREADLOCAL int command_i = 0; static THREADLOCAL int command_i = 0;

@ -1,7 +1,7 @@
#include <chipmunk.h> #include <chipmunk.h>
#define QUEUE_IMPL #define QUEUE_IMPL
#include "stdbool.h"
#include "queue.h" #include "queue.h"
#include "stdbool.h"
#include "types.h" #include "types.h"
#include "ipsettings.h" // debug/developer settings #include "ipsettings.h" // debug/developer settings
@ -30,7 +30,6 @@ void __assert(bool cond, const char* file, int line, const char* cond_string)
} }
} }
static V2 cp_to_v2(cpVect v) static V2 cp_to_v2(cpVect v)
{ {
return (V2){.x = (float)v.x, .y = (float)v.y}; return (V2){.x = (float)v.x, .y = (float)v.y};
@ -55,8 +54,10 @@ Entity* get_entity_even_if_dead(GameState* gs, EntityID id)
{ {
return NULL; return NULL;
} }
assert(id.index < gs->cur_next_entity || gs->cur_next_entity == 0); if (!(id.index < gs->cur_next_entity || gs->cur_next_entity == 0))
assert(id.index < gs->max_entities); return NULL;
if (!(id.index < gs->max_entities))
return NULL;
Entity *to_return = &gs->entities[id.index]; Entity *to_return = &gs->entities[id.index];
// don't validate the generation either // don't validate the generation either
return to_return; return to_return;
@ -386,17 +387,13 @@ static void grid_correct_for_holes(GameState* gs, struct Entity* grid)
V2 cur_local_pos = entity_shape_pos(N); V2 cur_local_pos = entity_shape_pos(N);
const V2 dirs[] = { const V2 dirs[] = {
(V2){ (V2){
.x = -1.0f, .y = 0.0f .x = -1.0f, .y = 0.0f},
},
(V2){ (V2){
.x = 1.0f, .y = 0.0f .x = 1.0f, .y = 0.0f},
},
(V2){ (V2){
.x = 0.0f, .y = 1.0f .x = 0.0f, .y = 1.0f},
},
(V2){ (V2){
.x = 0.0f, .y = -1.0f .x = 0.0f, .y = -1.0f},
},
}; };
int num_dirs = sizeof(dirs) / sizeof(*dirs); int num_dirs = sizeof(dirs) / sizeof(*dirs);
@ -557,6 +554,7 @@ V2 grid_world_to_local(Entity* grid, V2 world)
} }
V2 grid_local_to_world(Entity *grid, V2 local) V2 grid_local_to_world(Entity *grid, V2 local)
{ {
assert(grid->is_grid);
return cp_to_v2(cpBodyLocalToWorld(grid->body, v2_to_cp(local))); return cp_to_v2(cpBodyLocalToWorld(grid->body, v2_to_cp(local)));
} }
// returned snapped position is in world coordinates // returned snapped position is in world coordinates
@ -774,6 +772,8 @@ enum GameVersion
VAddedLastUsedMedbay, VAddedLastUsedMedbay,
VAddedSquads, VAddedSquads,
VAddedSquadInvites, VAddedSquadInvites,
VRemovedTimeFromDiskSave, // did this to avoid wayy too big a time causing precision problems
VReallyRemovedTimeFromDiskSave, // apparently last one didn't work
VMax, // this minus one will be the version used VMax, // this minus one will be the version used
}; };
@ -810,7 +810,6 @@ SerMaybeFailure ser_entityid(SerState* ser, EntityID* id)
SerMaybeFailure ser_inputframe(SerState *ser, InputFrame *i) SerMaybeFailure ser_inputframe(SerState *ser, InputFrame *i)
{ {
SER_VAR(&i->tick); SER_VAR(&i->tick);
SER_VAR(&i->id);
SER_MAYBE_RETURN(ser_V2(ser, &i->movement)); SER_MAYBE_RETURN(ser_V2(ser, &i->movement));
SER_VAR(&i->take_over_squad); SER_VAR(&i->take_over_squad);
SER_ASSERT(i->take_over_squad >= 0 || i->take_over_squad == -1); SER_ASSERT(i->take_over_squad >= 0 || i->take_over_squad == -1);
@ -823,9 +822,7 @@ SerMaybeFailure ser_inputframe(SerState* ser, InputFrame* i)
} }
SER_VAR(&i->seat_action); SER_VAR(&i->seat_action);
SER_MAYBE_RETURN(ser_entityid(ser, &i->seat_to_inhabit));
SER_MAYBE_RETURN(ser_V2(ser, &i->hand_pos)); SER_MAYBE_RETURN(ser_V2(ser, &i->hand_pos));
SER_MAYBE_RETURN(ser_entityid(ser, &i->grid_hand_pos_local_to));
SER_VAR(&i->dobuild); SER_VAR(&i->dobuild);
SER_VAR(&i->build_type); SER_VAR(&i->build_type);
@ -855,7 +852,7 @@ SerMaybeFailure ser_player(SerState* ser, Player* p)
SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e) SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e)
{ {
SER_VAR(&e->no_save_to_disk); // this is always false when saving to disk? SER_VAR(&e->no_save_to_disk); // @Robust this is always false when saving to disk?
SER_VAR(&e->generation); SER_VAR(&e->generation);
SER_VAR(&e->damage); SER_VAR(&e->damage);
@ -1023,16 +1020,11 @@ SerMaybeFailure ser_server_to_client(SerState* ser, ServerToClient* s)
SER_ASSERT(ser->version < VMax); SER_ASSERT(ser->version < VMax);
if (!ser->save_or_load_from_disk) if (!ser->save_or_load_from_disk)
SER_MAYBE_RETURN(ser_opus_packets(ser, s->playback_buffer)); SER_MAYBE_RETURN(ser_opus_packets(ser, s->audio_playback_buffer));
GameState *gs = s->cur_gs; GameState *gs = s->cur_gs;
int cur_next_entity = 0; // completely reset and destroy all gamestate data
if (ser->serializing)
cur_next_entity = gs->cur_next_entity;
SER_VAR(&cur_next_entity);
SER_ASSERT(cur_next_entity <= ser->max_entity_index);
if (!ser->serializing) if (!ser->serializing)
{ {
// avoid a memset here very expensive. que rico! // avoid a memset here very expensive. que rico!
@ -1041,8 +1033,23 @@ SerMaybeFailure ser_server_to_client(SerState* ser, ServerToClient* s)
gs->cur_next_entity = 0; // updated on deserialization gs->cur_next_entity = 0; // updated on deserialization
} }
int cur_next_entity = 0;
if (ser->serializing)
cur_next_entity = gs->cur_next_entity;
SER_VAR(&cur_next_entity);
SER_ASSERT(cur_next_entity <= ser->max_entity_index);
if (!ser->save_or_load_from_disk)
SER_MAYBE_RETURN(ser_entityid(ser, &gs->cur_spacestation));
SER_VAR(&s->your_player); SER_VAR(&s->your_player);
if (ser->version >= VReallyRemovedTimeFromDiskSave && ser->save_or_load_from_disk)
{
}
else
{
SER_VAR(&gs->time); SER_VAR(&gs->time);
}
SER_MAYBE_RETURN(ser_V2(ser, &gs->goldpos)); SER_MAYBE_RETURN(ser_V2(ser, &gs->goldpos));
@ -1228,13 +1235,47 @@ bool server_to_client_deserialize(struct ServerToClient* msg, char* bytes, size_
} }
} }
// only serializes up to the maximum inputs the server holds
SerMaybeFailure ser_client_to_server(SerState *ser, ClientToServer *msg) SerMaybeFailure ser_client_to_server(SerState *ser, ClientToServer *msg)
{ {
SER_VAR(&ser->version); SER_VAR(&ser->version);
SER_MAYBE_RETURN(ser_opus_packets(ser, msg->mic_data)); SER_MAYBE_RETURN(ser_opus_packets(ser, msg->mic_data));
for (int i = 0; i < INPUT_BUFFER; i++)
// serialize input packets
size_t num;
if (ser->serializing)
{ {
SER_MAYBE_RETURN(ser_inputframe(ser, &msg->inputs[i])); num = queue_num_elements(msg->input_data);
if (num > INPUT_QUEUE_MAX)
num = INPUT_QUEUE_MAX;
}
SER_VAR(&num);
SER_ASSERT(num <= INPUT_QUEUE_MAX);
if (ser->serializing)
{
size_t to_skip = queue_num_elements(msg->input_data) - num;
size_t i = 0;
QUEUE_ITER(msg->input_data, cur_header)
{
if (i < to_skip)
{
i++;
}
else
{
InputFrame *cur = (InputFrame *)cur_header->data;
SER_MAYBE_RETURN(ser_inputframe(ser, cur));
}
}
}
else
{
for (size_t i = 0; i < num; i++)
{
InputFrame *new_frame = (InputFrame *)queue_push_element(msg->input_data);
SER_ASSERT(new_frame != NULL);
SER_MAYBE_RETURN(ser_inputframe(ser, new_frame));
}
} }
return ser_ok; return ser_ok;
} }
@ -1295,8 +1336,8 @@ bool client_to_server_deserialize(GameState* gs, struct ClientToServer* msg, cha
} }
// has to be global var because can only get this information // has to be global var because can only get this information
static cpShape* closest_to_point_in_radius_result = NULL; static THREADLOCAL cpShape *closest_to_point_in_radius_result = NULL;
static float closest_to_point_in_radius_result_largest_dist = 0.0f; static THREADLOCAL float closest_to_point_in_radius_result_largest_dist = 0.0f;
static void closest_point_callback_func(cpShape *shape, cpContactPointSet *points, void *data) static void closest_point_callback_func(cpShape *shape, cpContactPointSet *points, void *data)
{ {
assert(points->count == 1); assert(points->count == 1);
@ -1380,17 +1421,24 @@ uint64_t tick(GameState* gs)
return (uint64_t)floor(gs->time / ((double)TIMESTEP)); return (uint64_t)floor(gs->time / ((double)TIMESTEP));
} }
V2 get_world_hand_pos(GameState* gs, InputFrame* input, Entity* player) Entity *grid_to_build_on(GameState *gs, V2 world_hand_pos)
{ {
Entity* potential_grid = get_entity(gs, input->grid_hand_pos_local_to); return closest_to_point_in_radius(gs, world_hand_pos, BUILD_BOX_SNAP_DIST_TO_SHIP);
}
V2 potentially_snap_hand_pos(GameState *gs, V2 world_hand_pos)
{
Entity *potential_grid = grid_to_build_on(gs, world_hand_pos);
if (potential_grid != NULL) if (potential_grid != NULL)
{ {
return grid_local_to_world(potential_grid, input->hand_pos); world_hand_pos = grid_snapped_box_pos(potential_grid, world_hand_pos);
} }
else return world_hand_pos;
{
return V2add(entity_pos(player), input->hand_pos);
} }
V2 get_world_hand_pos(GameState *gs, InputFrame *input, Entity *player)
{
return potentially_snap_hand_pos(gs, V2add(entity_pos(player), input->hand_pos));
} }
// return true if used the energy // return true if used the energy
@ -1439,7 +1487,7 @@ EntityID create_spacestation(GameState* gs)
Entity *grid = new_entity(gs); Entity *grid = new_entity(gs);
grid_create(gs, grid); grid_create(gs, grid);
grid->no_save_to_disk = true; grid->no_save_to_disk = true;
entity_set_pos(grid, (V2) { -150.0f, 0.0f }); entity_set_pos(grid, (V2){-15.0f, 0.0f});
entity_ensure_in_orbit(grid); entity_ensure_in_orbit(grid);
Entity *explosion_box = new_entity(gs); Entity *explosion_box = new_entity(gs);
box_create(gs, explosion_box, grid, (V2){0}); box_create(gs, explosion_box, grid, (V2){0});
@ -1478,11 +1526,15 @@ void exit_seat(GameState* gs, Entity* seat_in, Entity* player)
cpBodySetVelocity(player->body, v2_to_cp(player_vel(gs, player))); cpBodySetVelocity(player->body, v2_to_cp(player_vel(gs, player)));
} }
void process_fixed_timestep(GameState *gs)
{
process(gs, TIMESTEP);
}
void process(GameState *gs, float dt) void process(GameState *gs, float dt)
{ {
assert(gs->space != NULL); assert(gs->space != NULL);
assert(dt == TIMESTEP); // @TODO fix tick being incremented every time
gs->time += dt; gs->time += dt;
// process input // process input
@ -1494,7 +1546,8 @@ void process(GameState* gs, float dt)
{ {
player->squad = SquadNone; player->squad = SquadNone;
} }
else { else
{
bool squad_taken = false; bool squad_taken = false;
PLAYERS_ITER(gs->players, other_player) PLAYERS_ITER(gs->players, other_player)
{ {
@ -1601,18 +1654,19 @@ void process(GameState* gs, float dt)
// process movement // process movement
{ {
// no cheating by making movement bigger than length 1 // no cheating by making movement bigger than length 1
float movement_strength = V2length(player->input.movement); V2 movement_this_tick = (V2){0};
if (movement_strength != 0.0f) if (V2length(player->input.movement) > 0.0f)
{ {
player->input.movement = V2scale(V2normalize(player->input.movement), clamp(V2length(player->input.movement), 0.0f, 1.0f)); movement_this_tick = V2scale(V2normalize(player->input.movement), clamp(V2length(player->input.movement), 0.0f, 1.0f));
player->input.movement = (V2){0};
} }
Entity *seat_inside_of = get_entity(gs, p->currently_inside_of_box); Entity *seat_inside_of = get_entity(gs, p->currently_inside_of_box);
if (seat_inside_of == NULL) if (seat_inside_of == NULL)
{ {
cpShapeSetFilter(p->shape, PLAYER_SHAPE_FILTER); cpShapeSetFilter(p->shape, PLAYER_SHAPE_FILTER);
cpBodyApplyForceAtWorldPoint(p->body, v2_to_cp(V2scale(player->input.movement, PLAYER_JETPACK_FORCE)), cpBodyGetPosition(p->body)); cpBodyApplyForceAtWorldPoint(p->body, v2_to_cp(V2scale(movement_this_tick, PLAYER_JETPACK_FORCE)), cpBodyGetPosition(p->body));
p->damage += movement_strength * dt * PLAYER_JETPACK_SPICE_PER_SECOND; p->damage += V2length(movement_this_tick) * dt * PLAYER_JETPACK_SPICE_PER_SECOND;
} }
else else
{ {
@ -1627,9 +1681,9 @@ void process(GameState* gs, float dt)
Entity *g = get_entity(gs, seat_inside_of->shape_parent_entity); Entity *g = get_entity(gs, seat_inside_of->shape_parent_entity);
V2 target_direction = {0}; V2 target_direction = {0};
if (V2length(player->input.movement) > 0.0f) if (V2length(movement_this_tick) > 0.0f)
{ {
target_direction = V2normalize(player->input.movement); target_direction = V2normalize(movement_this_tick);
} }
BOXES_ITER(gs, cur, g) BOXES_ITER(gs, cur, g)
{ {
@ -1652,7 +1706,7 @@ void process(GameState* gs, float dt)
V2 world_build = world_hand_pos; V2 world_build = world_hand_pos;
// @Robust sanitize this input so player can't build on any grid in the world // @Robust sanitize this input so player can't build on any grid in the world
Entity* target_grid = get_entity(gs, player->input.grid_hand_pos_local_to); Entity *target_grid = grid_to_build_on(gs, world_hand_pos);
cpShape *nearest = cpSpacePointQueryNearest(gs->space, v2_to_cp(world_build), 0.01f, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &info); cpShape *nearest = cpSpacePointQueryNearest(gs->space, v2_to_cp(world_build), 0.01f, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &info);
if (nearest != NULL) if (nearest != NULL)
{ {

649
main.c

File diff suppressed because it is too large Load Diff

@ -1,17 +1,21 @@
#pragma once #pragma once
#include <stdbool.h>
#ifndef QUEUE_ASSERT #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) #define QUEUE_ASSERT(condition) __assert(condition, __FILE__, __LINE__, #condition)
#endif #endif
typedef struct QueueElementHeader { typedef struct QueueElementHeader
{
bool exists; bool exists;
struct QueueElementHeader *next; struct QueueElementHeader *next;
char data[]; char data[];
} QueueElementHeader; } QueueElementHeader;
typedef struct Queue { typedef struct Queue
{
char *data; char *data;
size_t data_length; // must be a multiple of sizeof(QueueElementHeader) + element_size size_t data_length; // must be a multiple of sizeof(QueueElementHeader) + element_size
size_t element_size; size_t element_size;
@ -20,11 +24,14 @@ typedef struct Queue {
#define QUEUE_SIZE_FOR_ELEMENTS(element_size, max_elements) ((sizeof(QueueElementHeader) + element_size) * max_elements) #define QUEUE_SIZE_FOR_ELEMENTS(element_size, max_elements) ((sizeof(QueueElementHeader) + element_size) * max_elements)
// 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_init(Queue *q, size_t element_size, char *data, size_t data_length);
void queue_clear(Queue *q); void queue_clear(Queue *q);
void *queue_push_element(Queue *q); void *queue_push_element(Queue *q);
size_t queue_num_elements(Queue *q); size_t queue_num_elements(Queue *q);
void *queue_pop_element(Queue *q); void *queue_pop_element(Queue *q);
void *queue_most_recent_element(Queue *q);
#ifdef QUEUE_IMPL #ifdef QUEUE_IMPL
@ -68,7 +75,8 @@ void* queue_push_element(Queue* q)
{ {
return NULL; return NULL;
} }
else { else
{
to_return->exists = true; to_return->exists = true;
to_return->next = NULL; // very important. to_return->next = NULL; // very important.
for (size_t i = 0; i < q->element_size; i++) for (size_t i = 0; i < q->element_size; i++)
@ -78,10 +86,12 @@ void* queue_push_element(Queue* q)
if (q->next != NULL) if (q->next != NULL)
{ {
QueueElementHeader *cur = q->next; QueueElementHeader *cur = q->next;
while (cur->next != NULL) cur = cur->next; while (cur->next != NULL)
cur = cur->next;
cur->next = to_return; cur->next = to_return;
} }
else { else
{
q->next = to_return; q->next = to_return;
} }
@ -94,7 +104,8 @@ size_t queue_num_elements(Queue* q)
QUEUE_ASSERT(q->data != NULL); QUEUE_ASSERT(q->data != NULL);
size_t to_return = 0; size_t to_return = 0;
QUEUE_ELEM_ITER(cur) QUEUE_ELEM_ITER(cur)
if (cur->exists) to_return++; if (cur->exists)
to_return++;
return to_return; return to_return;
} }
@ -103,10 +114,24 @@ void* queue_pop_element(Queue* q)
{ {
QUEUE_ASSERT(q->data != NULL); QUEUE_ASSERT(q->data != NULL);
QueueElementHeader *to_return = q->next; QueueElementHeader *to_return = q->next;
if (q->next != NULL) q->next = q->next->next; if (q->next != NULL)
if (to_return != NULL) to_return->exists = false; // jank! q->next = q->next->next;
if (to_return != NULL)
to_return->exists = false; // jank!
return to_return == NULL ? NULL : (void *)to_return->data; 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 #undef QUEUE_ELEM_ITER
#endif #endif

@ -1,12 +1,12 @@
#include "sokol_time.h"
#include <chipmunk.h> // initializing bodies #include <chipmunk.h> // initializing bodies
#include "types.h" #include "types.h"
#include "sokol_time.h"
#include <enet/enet.h> #include <enet/enet.h>
#include <stdio.h> #include <errno.h>
#include <inttypes.h> // int64 printing #include <inttypes.h> // int64 printing
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> // error string #include <string.h> // error string
#include <errno.h>
#include "minilzo.h" #include "minilzo.h"
@ -31,9 +31,11 @@
#define NOMINMAX #define NOMINMAX
#include <Windows.h> #include <Windows.h>
// This is slow, if you can use RDTSC and set the multiplier in SpallInit, you'll have far better timing accuracy // 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; static double invfreq;
if (!invfreq) { if (!invfreq)
{
LARGE_INTEGER frequency; LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency); QueryPerformanceFrequency(&frequency);
invfreq = 1000000.0 / frequency.QuadPart; invfreq = 1000000.0 / frequency.QuadPart;
@ -70,16 +72,23 @@ void server(void* info_raw)
#endif #endif
struct GameState gs = {0}; struct GameState gs = {0};
size_t entities_size = (sizeof(Entity) * MAX_ENTITIES); 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); initialize(&gs, entity_data, entities_size);
Log("Allocated %zu bytes for entities\n", entities_size); Log("Allocated %zu bytes for entities\n", entities_size);
// 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}; Queue player_voip_buffers[MAX_PLAYERS] = {0};
size_t player_voip_buffer_size = QUEUE_SIZE_FOR_ELEMENTS(sizeof(OpusPacket), VOIP_PACKET_BUFFER_SIZE); 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); 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}; OpusEncoder *player_encoders[MAX_PLAYERS] = {0};
OpusDecoder *player_decoders[MAX_PLAYERS] = {0}; OpusDecoder *player_decoders[MAX_PLAYERS] = {0};
@ -189,14 +198,15 @@ void server(void* info_raw)
uint64_t last_processed_time = stm_now(); uint64_t last_processed_time = stm_now();
uint64_t last_saved_world_time = stm_now(); uint64_t last_saved_world_time = stm_now();
uint64_t last_sent_audio_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 audio_time_to_send = 0.0f;
float total_time = 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) while (true)
{ {
ma_mutex_lock(&info->info_mutex); ma_mutex_lock(&info->info_mutex);
if (info->should_quit) { if (info->should_quit)
{
ma_mutex_unlock(&info->info_mutex); ma_mutex_unlock(&info->info_mutex);
break; break;
} }
@ -241,7 +251,6 @@ void server(void* info_raw)
event.peer->data = (void *)player_slot; event.peer->data = (void *)player_slot;
gs.players[player_slot] = (struct Player){0}; gs.players[player_slot] = (struct Player){0};
gs.players[player_slot].connected = true; gs.players[player_slot].connected = true;
player_to_latest_id_processed[player_slot] = 0;
int error; int error;
player_encoders[player_slot] = opus_encoder_create(VOIP_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &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"); Log("Wtf an empty packet from enet?\n");
} }
else { else
{
int64_t player_slot = (int64_t)event.peer->data; int64_t player_slot = (int64_t)event.peer->data;
size_t length = event.packet->dataLength; 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); VOIP_QUEUE_DECL(throwaway_buffer, throwaway_buffer_data);
Queue *buffer_to_fill = &player_voip_buffers[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 (get_entity(&gs, gs.players[player_slot].entity) == NULL)
struct ClientToServer received = { .mic_data = buffer_to_fill }; buffer_to_fill = &throwaway_buffer;
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];
if (received.inputs[0].id > latest_id) queue_clear(&player_input_queues[player_slot]);
{ struct ClientToServer received = {.mic_data = buffer_to_fill, .input_data = &player_input_queues[player_slot]};
for (int i = INPUT_BUFFER - 1; i >= 0; i--) char decompressed[MAX_CLIENT_TO_SERVER] = {0};
{ size_t decompressed_max_len = MAX_CLIENT_TO_SERVER;
if (received.inputs[i].tick == 0) // empty input assert(LZO1X_MEM_DECOMPRESS == 0);
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;
// for these "event" inputs, only modify the current input if the event is true. int return_value = lzo1x_decompress_safe(event.packet->data, event.packet->dataLength, decompressed, &decompressed_max_len, NULL);
// 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 (return_value == LZO_E_OK)
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)
{ {
gs.players[player_slot].input.seat_action = cur_input.seat_action; 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;
}
if (cur_input.dobuild)
{ {
gs.players[player_slot].input.grid_hand_pos_local_to = cur_input.grid_hand_pos_local_to; Log("Bad packet from client %d\n", (int)player_slot);
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;
}
} }
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. */ /* Clean up the packet now that we're done using it. */
@ -356,7 +340,6 @@ void server(void* info_raw)
break; break;
} }
} }
} }
total_time += (float)stm_sec(stm_diff(stm_now(), last_processed_time)); total_time += (float)stm_sec(stm_diff(stm_now(), last_processed_time));
last_processed_time = stm_now(); last_processed_time = stm_now();
@ -368,12 +351,24 @@ void server(void* info_raw)
total_time = max_time; total_time = max_time;
} }
bool processed = false;
PROFILE_SCOPE("World Processing") PROFILE_SCOPE("World Processing")
{ {
while (total_time > TIMESTEP) 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); process(&gs, TIMESTEP);
total_time -= 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) 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(); last_saved_world_time = stm_now();
ServerToClient msg = (ServerToClient){ ServerToClient msg = (ServerToClient){
.cur_gs = &gs, .cur_gs = &gs,
@ -416,9 +412,11 @@ void server(void* info_raw)
} }
} }
if (processed) if (stm_sec(stm_diff(stm_now(), last_sent_gamestate_time)) > TIME_BETWEEN_SEND_GAMESTATE)
{
last_sent_gamestate_time = stm_now();
PROFILE_SCOPE("send_data")
{ {
PROFILE_SCOPE("send_data") {
static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = {0}; 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)); audio_time_to_send += (float)stm_sec(stm_diff(stm_now(), last_sent_audio_time));
@ -494,7 +492,8 @@ void server(void* info_raw)
{ {
Log("Failed to encode audio packet for player %d: opus error code %d\n", this_player_index, ret); Log("Failed to encode audio packet for player %d: opus error code %d\n", this_player_index, ret);
} }
else { else
{
this_packet->length = ret; this_packet->length = ret;
} }
} }
@ -502,7 +501,7 @@ void server(void* info_raw)
ServerToClient to_send = (ServerToClient){ ServerToClient to_send = (ServerToClient){
.cur_gs = &gs, .cur_gs = &gs,
.your_player = this_player_index, .your_player = this_player_index,
.playback_buffer = &buffer_to_play, .audio_playback_buffer = &buffer_to_play,
}; };
size_t len = 0; size_t len = 0;
@ -536,7 +535,6 @@ void server(void* info_raw)
free(compressed_buffer); free(compressed_buffer);
} }
} }
} }
} }
for (int i = 0; i < MAX_PLAYERS; i++) for (int i = 0; i < MAX_PLAYERS; i++)
@ -546,7 +544,10 @@ void server(void* info_raw)
if (player_decoders[i] != NULL) if (player_decoders[i] != NULL)
opus_decoder_destroy(player_decoders[i]); 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); free(world_save_buffer);
destroy(&gs); destroy(&gs);
free(entity_data); free(entity_data);

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "ipsettings.h"
#define MAX_PLAYERS 16 #define MAX_PLAYERS 16
#define MAX_ENTITIES 1024 * 25 #define MAX_ENTITIES 1024 * 25
#define BOX_SIZE 0.25f #define BOX_SIZE 0.25f
@ -21,7 +23,11 @@
#define SUN_RADIUS 10.0f #define SUN_RADIUS 10.0f
#define INSTANT_DEATH_DISTANCE_FROM_SUN 2000.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) #define SUN_GRAVITY_STRENGTH (9.0e2f)
#endif
#define SOLAR_ENERGY_PER_SECOND 0.02f #define SOLAR_ENERGY_PER_SECOND 0.02f
#define DAMAGE_TO_PLAYER_PER_BLOCK 0.1f #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
@ -38,15 +44,24 @@
#define VOIP_PACKET_BUFFER_SIZE 15 // audio. Must be bigger than 2 #define VOIP_PACKET_BUFFER_SIZE 15 // audio. Must be bigger than 2
#define VOIP_EXPECTED_FRAME_COUNT 480 #define VOIP_EXPECTED_FRAME_COUNT 480
#define VOIP_SAMPLE_RATE 48000 #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_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_SEND_GAMESTATE (1.0f / 20.0f)
#define TIME_BETWEEN_INPUT_PACKETS (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 TIMESTEP (1.0f / 60.0f) // server required to simulate at this, defines what tick the game is on
#define SERVER_PORT 2551 #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" // must make this header and set the target address, just #define SERVER_ADDRESS "127.0.0.1"
#include "ipsettings.h" // don't leak IP! #include "ipsettings.h" // don't leak IP!
@ -85,8 +100,8 @@ typedef void cpBody;
typedef void cpShape; typedef void cpShape;
#endif #endif
#include <stdbool.h>
#include "queue.h" #include "queue.h"
#include <stdbool.h>
#ifndef OPUS_TYPES_H #ifndef OPUS_TYPES_H
typedef int opus_int32; typedef int opus_int32;
@ -103,9 +118,11 @@ typedef int opus_int32;
typedef sgp_vec2 V2; typedef sgp_vec2 V2;
typedef sgp_point P2; typedef sgp_point P2;
#define Log(...){ \ #define Log(...) \
{ \
fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); \ fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); \
fprintf(stdout, __VA_ARGS__);} fprintf(stdout, __VA_ARGS__); \
}
enum BoxType enum BoxType
{ {
@ -155,7 +172,6 @@ static bool entityids_same(EntityID a, EntityID b)
typedef struct InputFrame typedef struct InputFrame
{ {
uint64_t tick; 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; V2 movement;
int take_over_squad; // -1 means not taking over any squad 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. 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; bool seat_action;
EntityID seat_to_inhabit; V2 hand_pos; // local to player transationally but not rotationally
V2 hand_pos; // local to player transationally but not rotationally unless field below is not null, then it's local to that grid
// @BeforeShip bounds check on the hand_pos so that players can't reach across the entire map // @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; bool dobuild;
enum BoxType build_type; enum BoxType build_type;
@ -240,7 +254,7 @@ typedef struct GameState
{ {
cpSpace *space; cpSpace *space;
double time; double time; // @Robust separate tick integer not prone to precision issues
V2 goldpos; V2 goldpos;
@ -258,7 +272,9 @@ typedef struct GameState
EntityID free_list; EntityID free_list;
} GameState; } 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 PI 3.14159f
#define TAU (PI * 2.0f) #define TAU (PI * 2.0f)
@ -287,7 +303,8 @@ static float rotangle(enum CompassRotation rot)
} }
} }
typedef struct OpusPacket { typedef struct OpusPacket
{
opus_int32 length; opus_int32 length;
char data[VOIP_PACKET_MAX_SIZE]; char data[VOIP_PACKET_MAX_SIZE];
} OpusPacket; } OpusPacket;
@ -295,14 +312,14 @@ typedef struct OpusPacket {
typedef struct ServerToClient typedef struct ServerToClient
{ {
struct GameState *cur_gs; struct GameState *cur_gs;
Queue* playback_buffer; Queue *audio_playback_buffer;
int your_player; int your_player;
} ServerToClient; } ServerToClient;
typedef struct ClientToServer typedef struct ClientToServer
{ {
Queue *mic_data; // on serialize, flushes this of packets. On deserialize, fills it Queue *mic_data; // on serialize, flushes this of packets. On deserialize, fills it
InputFrame inputs[INPUT_BUFFER]; Queue *input_data; // does not flush on serialize! must be in order of tick
} ClientToServer; } ClientToServer;
#define DeferLoop(start, end) \ #define DeferLoop(start, end) \
@ -315,6 +332,7 @@ void server(void* info); // data parameter required from thread api...
EntityID create_spacestation(GameState *gs); EntityID create_spacestation(GameState *gs);
void initialize(struct GameState *gs, void *entity_arena, size_t entity_arena_size); void initialize(struct GameState *gs, void *entity_arena, size_t entity_arena_size);
void destroy(struct GameState *gs); void destroy(struct GameState *gs);
void process_fixed_timestep(GameState *gs);
void process(struct GameState *gs, float dt); // does in place void process(struct GameState *gs, float dt); // does in place
Entity *closest_to_point_in_radius(struct GameState *gs, V2 point, float radius); Entity *closest_to_point_in_radius(struct GameState *gs, V2 point, float radius);
uint64_t tick(struct GameState *gs); uint64_t tick(struct GameState *gs);
@ -360,7 +378,8 @@ void dbg_drawall();
void dbg_line(V2 from, V2 to); void dbg_line(V2 from, V2 to);
void dbg_rect(V2 center); void dbg_rect(V2 center);
typedef struct ServerThreadInfo { typedef struct ServerThreadInfo
{
ma_mutex info_mutex; ma_mutex info_mutex;
const char *world_save; const char *world_save;
bool should_quit; bool should_quit;
@ -374,8 +393,7 @@ typedef struct AABB
static AABB centered_at(V2 point, V2 size) static AABB centered_at(V2 point, V2 size)
{ {
return (AABB) return (AABB){
{
.x = point.x - size.x / 2.0f, .x = point.x - size.x / 2.0f,
.y = point.y - size.y / 2.0f, .y = point.y - size.y / 2.0f,
.width = size.x, .width = size.x,
@ -419,7 +437,6 @@ static V2 V2normalize(V2 v)
return V2scale(v, 1.0f / V2length(v)); return V2scale(v, 1.0f / V2length(v));
} }
static float V2dot(V2 a, V2 b) static float V2dot(V2 a, V2 b)
{ {
return a.x * b.x + a.y * b.y; return a.x * b.x + a.y * b.y;
@ -497,7 +514,8 @@ static float lerp(float a, float b, float f)
return a * (1.0f - f) + (b * 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 difference = fmodf(p_to - p_from, (float)TAU);
float distance = fmodf(2.0f * difference, (float)TAU) - difference; float distance = fmodf(2.0f * difference, (float)TAU) - difference;
return p_from + distance * p_weight; return p_from + distance * p_weight;

Loading…
Cancel
Save