diff --git a/gamestate.c b/gamestate.c index aee116c..7970ad0 100644 --- a/gamestate.c +++ b/gamestate.c @@ -89,8 +89,14 @@ void grid_destroy(cpSpace *space, struct Grid *grid) // removes boxe from grid, then ensures that the rule that grids must not have // holes in them is applied. // uses these forward declared serialization functions to duplicate a box -void ser_box(char **out, struct Box *b); -void des_box(char **in, struct Box *new_box, struct GameState *gs, struct Grid *g); +typedef struct SerState +{ + char *bytes; + bool serializing; + int cursor; // points to next available byte, is the size of current message after serializing something + int max_size; +} SerState; +void ser_box(SerState *ser, struct Box *var, struct GameState *gs, struct Grid *g); static void grid_remove_box(cpSpace *space, struct Grid *grid, struct Box *box) { box_destroy(space, box); @@ -252,11 +258,19 @@ static void grid_remove_box(cpSpace *space, struct Grid *grid, struct Box *box) while (cur_separate_grid[cur_sepgrid_i] != NULL) { char box_bytes[128]; + char *cur = box_bytes; // duplicate the box by serializing it then deserializing it - ser_box(&cur, cur_separate_grid[cur_sepgrid_i]); - cur = box_bytes; - des_box(&cur, &new_grid->boxes[new_grid_box_i], gs, new_grid); + SerState ser = (SerState){ + .bytes = cur, + .cursor = 0, + .max_size = 128, + .serializing = true, + }; + ser_box(&ser, cur_separate_grid[cur_sepgrid_i], gs, grid); + ser.cursor = 0; + ser.serializing = false; + ser_box(&ser, &new_grid->boxes[new_grid_box_i], gs, new_grid); cur_sepgrid_i++; new_grid_box_i++; @@ -426,301 +440,221 @@ float box_rotation(struct Box *box) return cpBodyGetAngle(cpShapeGetBody(box->shape)); } -#define memwrite(out, variable) \ - for (char b = 0; b < sizeof(variable); b++) \ - { \ - **out = ((char *)&variable)[b]; \ - *out += 1; \ - } - -#define memread(in, variable_pointer) \ - for (char b = 0; b < sizeof(*variable_pointer); b++) \ - { \ - ((char *)variable_pointer)[b] = **in; \ - *in += 1; \ +#define WRITE_VARNAMES false // good for debugging +#include +// assumes SerState *var defined +#define SER_VAR(var_pointer) \ + { \ + const char *var_name = #var_pointer; \ + size_t var_name_len = 0; \ + if (WRITE_VARNAMES) \ + { \ + var_name_len = strlen(var_name); \ + } \ + if (ser->serializing) \ + { \ + if (WRITE_VARNAMES) \ + { \ + memcpy(ser->bytes + ser->cursor, var_name, var_name_len); \ + ser->cursor += var_name_len; \ + } \ + for (int b = 0; b < sizeof(*var_pointer); b++) \ + { \ + ser->bytes[ser->cursor] = ((char *)var_pointer)[b]; \ + ser->cursor += 1; \ + assert(ser->cursor < ser->max_size); \ + } \ + } \ + else \ + { \ + if (WRITE_VARNAMES) \ + { \ + char *read_name = malloc(sizeof *read_name * (var_name_len + 1)); \ + for (int i = 0; i < var_name_len; i++) \ + { \ + read_name[i] = ser->bytes[ser->cursor]; \ + ser->cursor += 1; \ + assert(ser->cursor < ser->max_size); \ + } \ + read_name[var_name_len] = '\0'; \ + if (strcmp(read_name, var_name) != 0) \ + { \ + printf("%s:%d | Expected variable %s but got %s\n", __FILE__, __LINE__, var_name, read_name); \ + } \ + free(read_name); \ + } \ + for (int b = 0; b < sizeof(*var_pointer); b++) \ + { \ + ((char *)var_pointer)[b] = ser->bytes[ser->cursor]; \ + ser->cursor += 1; \ + assert(ser->cursor < ser->max_size); \ + } \ + } \ } -void ser_float(char **out, float f) +void ser_V2(SerState *ser, V2 *var) { - memwrite(out, f); + SER_VAR(&var->x); + SER_VAR(&var->y); } -void des_float(char **in, float *f) +void ser_box(SerState *ser, struct Box *var, struct GameState *gs, struct Grid *g) { - memread(in, f); -} - -void ser_double(char **out, double d) -{ - memwrite(out, d); -} - -void des_double(char **in, double *d) -{ - memread(in, d); -} - -void ser_int(char **out, int i) -{ - memwrite(out, i); -} - -void des_int(char **in, int *i) -{ - memread(in, i); -} - -void ser_uint64(char **out, uint64_t i) -{ - memwrite(out, i); -} - -void des_uint64(char **in, uint64_t *i) -{ - memread(in, i); -} - -void ser_bool(char **out, bool b) -{ - **out = (char)b; - *out += 1; -} - -void des_bool(char **in, bool *b) -{ - *b = (bool)**in; - *in += 1; -} - -void ser_V2(char **out, V2 v) -{ - ser_float(out, v.x); - ser_float(out, v.y); -} - -void des_V2(char **in, V2 *v) -{ - des_float(in, &v->x); - des_float(in, &v->y); -} - -void ser_box(char **out, struct Box *b) -{ - ser_V2(out, cp_to_v2(cpShapeGetCenterOfGravity(b->shape))); - - ser_int(out, b->type); // @Robust separate enum serialization that checks for out of bounds enum - ser_int(out, b->compass_rotation); - ser_float(out, b->thrust); - ser_float(out, b->energy_used); - ser_float(out, b->damage); -} + { + V2 pos; + if (ser->serializing) + { + pos = cp_to_v2(cpShapeGetCenterOfGravity(var->shape)); + } + ser_V2(ser, &pos); + if (!ser->serializing) + { + box_new(var, gs, g, pos); + } + } -void des_box(char **in, struct Box *new_box, struct GameState *gs, struct Grid *g) -{ - V2 pos = {0}; - des_V2(in, &pos); - box_new(new_box, gs, g, pos); - - des_int(in, (int *)&new_box->type); - des_int(in, (int *)&new_box->compass_rotation); - des_float(in, &new_box->thrust); - des_float(in, &new_box->energy_used); - des_float(in, &new_box->damage); + SER_VAR(&var->type); // @Rovarust separate enum serialization that checks for out of varounds enum + SER_VAR(&var->compass_rotation); + SER_VAR(&var->thrust); + SER_VAR(&var->energy_used); + SER_VAR(&var->damage); } -void ser_grid(char **out, struct Grid *g) +void ser_grid(SerState *ser, struct GameState *gs, struct Grid *g) { - // grid must not be null, dummy! - assert(g->body != NULL); - - ser_V2(out, grid_pos(g)); - ser_V2(out, grid_vel(g)); - ser_float(out, grid_rotation(g)); - ser_float(out, grid_angular_velocity(g)); + if (ser->serializing) + assert(g->body != NULL); + else + assert(g->body == NULL); - ser_float(out, g->total_energy_capacity); - - for (int i = 0; i < MAX_BOXES_PER_GRID; i++) { - bool exists = g->boxes[i].shape != NULL; - ser_bool(out, exists); - if (exists) + V2 pos = {0}; + V2 vel = {0}; + float rot = 0.0f; + float angular_vel = 0.0f; + if (ser->serializing) { - ser_box(out, &g->boxes[i]); + pos = grid_pos(g); + vel = grid_vel(g); + rot = grid_rotation(g); + angular_vel = grid_angular_velocity(g); + } + SER_VAR(&pos); + SER_VAR(&vel); + SER_VAR(&rot); + SER_VAR(&angular_vel); + if (!ser->serializing) + { + grid_new(g, gs, pos); + cpBodySetVelocity(g->body, v2_to_cp(vel)); + cpBodySetAngle(g->body, rot); + cpBodySetAngularVelocity(g->body, angular_vel); } } -} - -// takes gamestate as argument to place box in the gamestates space -void des_grid(char **in, struct Grid *g, struct GameState *gs) -{ - assert(g->body == NULL); // destroy the grid before deserializing into it - - V2 pos = {0}; - V2 vel = {0}; - float rot = 0.0f; - float angular_vel = 0.0f; - - des_V2(in, &pos); - des_V2(in, &vel); - des_float(in, &rot); - des_float(in, &angular_vel); - grid_new(g, gs, pos); - cpBodySetVelocity(g->body, v2_to_cp(vel)); - cpBodySetAngle(g->body, rot); - cpBodySetAngularVelocity(g->body, angular_vel); + SER_VAR(&g->total_energy_capacity); - des_float(in, &g->total_energy_capacity); - - // iterate over every box like this so box index is preserved on deserialization for (int i = 0; i < MAX_BOXES_PER_GRID; i++) { - bool exists = false; - des_bool(in, &exists); + bool exists; + if (ser->serializing) + exists = g->boxes[i].shape != NULL; + SER_VAR(&exists); if (exists) { - des_box(in, &g->boxes[i], gs, g); + ser_box(ser, &g->boxes[i], gs, g); } } } -void ser_inputframe(char **out, struct InputFrame *i) -{ - ser_V2(out, i->movement); - ser_bool(out, i->inhabit); - - ser_V2(out, i->build); - ser_bool(out, i->dobuild); - ser_int(out, i->build_type); - ser_int(out, i->build_rotation); - ser_int(out, i->grid_index); -} - -void des_inputframe(char **in, struct InputFrame *i) +void ser_inputframe(SerState *ser, struct InputFrame *i) { - des_V2(in, &i->movement); - des_bool(in, &i->inhabit); - - des_V2(in, &i->build); - des_bool(in, &i->dobuild); - des_int(in, (int *)&i->build_type); - des_int(in, (int *)&i->build_rotation); - des_int(in, &i->grid_index); + SER_VAR(&i->movement); + SER_VAR(&i->inhabit); + SER_VAR(&i->build); + SER_VAR(&i->dobuild); + SER_VAR(&i->build_type); + SER_VAR(&i->build_rotation); + SER_VAR(&i->grid_index); } -void ser_player(char **out, struct Player *p) +void ser_player(SerState *ser, struct Player *p) { - ser_bool(out, p->connected); + SER_VAR(&p->connected); if (p->connected) { - ser_int(out, p->currently_inhabiting_index); - ser_V2(out, p->pos); - ser_V2(out, p->vel); - ser_float(out, p->spice_taken_away); - ser_float(out, p->goldness); - - ser_inputframe(out, &p->input); + SER_VAR(&p->currently_inhabiting_index); + ser_V2(ser, &p->pos); + ser_V2(ser, &p->vel); + SER_VAR(&p->spice_taken_away); + SER_VAR(&p->goldness); + ser_inputframe(ser, &p->input); } } -void des_player(char **in, struct Player *p, struct GameState *gs) +void ser_server_to_client(SerState *ser, ServerToClient *s) { - des_bool(in, &p->connected); - if (p->connected) - { - des_int(in, &p->currently_inhabiting_index); - des_V2(in, &p->pos); - des_V2(in, &p->vel); - des_float(in, &p->spice_taken_away); - des_float(in, &p->goldness); + struct GameState *gs = s->cur_gs; - des_inputframe(in, &p->input); + if (!ser->serializing) + { + destroy(gs); + initialize(gs); } -} - -// @Robust really think about if <= makes more sense than < here... -#define LEN_CHECK() assert(bytes - original_bytes <= max_len) - -void into_bytes(struct ServerToClient *msg, char *bytes, int *out_len, int max_len) -{ - assert(msg->cur_gs != NULL); - assert(msg != NULL); - struct GameState *gs = msg->cur_gs; - char *original_bytes = bytes; - ser_int(&bytes, msg->your_player); - LEN_CHECK(); + SER_VAR(&s->your_player); + SER_VAR(&gs->tick); + SER_VAR(&gs->time); - ser_uint64(&bytes, gs->tick); - - ser_double(&bytes, gs->time); - LEN_CHECK(); - - ser_V2(&bytes, gs->goldpos); - LEN_CHECK(); + ser_V2(ser, &gs->goldpos); for (int i = 0; i < MAX_PLAYERS; i++) { - ser_player(&bytes, &gs->players[i]); - LEN_CHECK(); + ser_player(ser, &gs->players[i]); } - // @Robust invalid message on num boxes bigger than max boxes - for (int i = 0; i < MAX_GRIDS; i++) { - bool exists = gs->grids[i].body != NULL; - ser_bool(&bytes, exists); - LEN_CHECK(); + bool exists; + if (ser->serializing) + exists = gs->grids[i].body != NULL; + SER_VAR(&exists); if (exists) { - ser_grid(&bytes, &gs->grids[i]); - LEN_CHECK(); + ser_grid(ser, gs, &gs->grids[i]); } } - - *out_len = bytes - original_bytes; } -void from_bytes(struct ServerToClient *msg, char *bytes, int max_len) +void into_bytes(struct ServerToClient *msg, char *bytes, int *out_len, int max_len) { - struct GameState *gs = msg->cur_gs; - - char *original_bytes = bytes; - - destroy(gs); - initialize(gs); - - des_int(&bytes, &msg->your_player); - LEN_CHECK(); + assert(msg->cur_gs != NULL); + assert(msg != NULL); - des_uint64(&bytes, &gs->tick); - LEN_CHECK(); + SerState ser = (SerState){ + .bytes = bytes, + .serializing = true, + .cursor = 0, + .max_size = max_len, + }; - des_double(&bytes, &gs->time); - LEN_CHECK(); + ser_server_to_client(&ser, msg); + *out_len = ser.cursor + 1; // @Robust not sure why I need to add one to cursor, ser.cursor should be the length.. +} - des_V2(&bytes, &gs->goldpos); - LEN_CHECK(); +void from_bytes(struct ServerToClient *msg, char *bytes, int max_len) +{ + assert(msg->cur_gs != NULL); + assert(msg != NULL); - for (int i = 0; i < MAX_PLAYERS; i++) - { - des_player(&bytes, &gs->players[i], gs); - LEN_CHECK(); - } + SerState ser = (SerState){ + .bytes = bytes, + .serializing = false, + .cursor = 0, + .max_size = max_len, + }; - for (int i = 0; i < MAX_GRIDS; i++) - { - bool exists = false; - des_bool(&bytes, &exists); - LEN_CHECK(); - if (exists) - { - des_grid(&bytes, &gs->grids[i], gs); - LEN_CHECK(); - } - } + ser_server_to_client(&ser, msg); } // has to be global var because can only get this information diff --git a/types.h b/types.h index 28e6a36..a3fbfa6 100644 --- a/types.h +++ b/types.h @@ -171,11 +171,11 @@ static float rotangle(enum CompassRotation rot) } } -struct ServerToClient +typedef struct ServerToClient { struct GameState *cur_gs; int your_player; -}; +} ServerToClient; struct ClientToServer {