Multiple inputs per input packet

main
Cameron Murphy Reikes 2 years ago
parent 67f5542441
commit 3ec1103ec9

@ -268,6 +268,16 @@ 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;
@ -403,6 +413,8 @@ void into_bytes(struct ServerToClient *msg, char *bytes, int *out_len, int max_l
ser_int(&bytes, msg->your_player);
LEN_CHECK();
ser_uint64(&bytes, gs->tick);
ser_double(&bytes, gs->time);
LEN_CHECK();
@ -444,6 +456,9 @@ void from_bytes(struct ServerToClient *msg, char *bytes, int max_len)
des_int(&bytes, &msg->your_player);
LEN_CHECK();
des_uint64(&bytes, &gs->tick);
LEN_CHECK();
des_double(&bytes, &gs->time);
LEN_CHECK();
@ -523,6 +538,7 @@ void process(struct GameState *gs, float dt)
{
assert(gs->space != NULL);
gs->tick += 1;
gs->time += dt;
// process input
@ -569,16 +585,15 @@ void process(struct GameState *gs, float dt)
}
// don't allow inhabiting a grid that's already inhabited
for(int ii = 0; ii < MAX_PLAYERS; ii++)
for (int ii = 0; ii < MAX_PLAYERS; ii++)
{
if(gs->players[ii].currently_inhabiting_index == ship_to_inhabit)
if (gs->players[ii].currently_inhabiting_index == ship_to_inhabit)
{
Log("Attempted to inhabit already taken ship\n");
ship_to_inhabit = -1;
}
}
if (ship_to_inhabit == -1)
{
Log("Couldn't find ship to inhabit even though point collision returned something\n");

@ -29,8 +29,9 @@ static KeyPressed keypressed[SAPP_KEYCODE_MENU] = {0};
static V2 mouse_pos = {0};
static bool mouse_pressed = false;
static uint64_t mouse_pressed_frame = 0;
static bool mouse_frozen = false; // @BeforeShip make this debug only thing
static float funval = 0.0f; // easy to play with value controlled by left mouse button when held down @BeforeShip remove on release builds
static bool mouse_frozen = false; // @BeforeShip make this debug only thing
static float funval = 0.0f; // easy to play with value controlled by left mouse button when held down @BeforeShip remove on release builds
static struct ClientToServer client_to_server = {0}; // buffer of inputs
static ENetHost *client;
static ENetPeer *peer;
@ -151,7 +152,7 @@ static void frame(void)
{
int width = sapp_width(), height = sapp_height();
float ratio = width / (float)height;
float time = sapp_frame_count() * sapp_frame_duration();
double time = sapp_frame_count() * sapp_frame_duration();
float dt = sapp_frame_duration();
// pressed input management
@ -287,32 +288,54 @@ static void frame(void)
// Create and send input packet
{
// @Robust accumulate total time and send input at rate like 20 hz, not every frame
struct ClientToServer curmsg = {0};
struct InputFrame cur_input_frame = {0};
V2 input = (V2){
.x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A],
.y = (float)keydown[SAPP_KEYCODE_W] - (float)keydown[SAPP_KEYCODE_S],
};
curmsg.movement = input;
curmsg.inhabit = keypressed[SAPP_KEYCODE_G].pressed;
curmsg.dobuild = mouse_pressed;
curmsg.grid_index = grid_index;
if (curmsg.dobuild)
cur_input_frame.movement = input;
cur_input_frame.inhabit = keypressed[SAPP_KEYCODE_G].pressed;
cur_input_frame.dobuild = mouse_pressed;
cur_input_frame.grid_index = grid_index;
if (cur_input_frame.dobuild)
{
if (grid_index != -1)
{
curmsg.build = grid_world_to_local(&gs.grids[curmsg.grid_index], build_preview.pos);
V2 untransformed = grid_local_to_world(&gs.grids[curmsg.grid_index], curmsg.build);
cur_input_frame.build = grid_world_to_local(&gs.grids[cur_input_frame.grid_index], build_preview.pos);
V2 untransformed = grid_local_to_world(&gs.grids[cur_input_frame.grid_index], cur_input_frame.build);
untransformed.x += 5.0f;
}
else
{
curmsg.build = build_preview.pos;
cur_input_frame.build = build_preview.pos;
}
}
struct InputFrame latest = client_to_server.inputs[0];
// if they're not the same
if (
!V2cmp(cur_input_frame.movement, latest.movement, 0.01f) ||
cur_input_frame.inhabit != latest.inhabit ||
cur_input_frame.dobuild != latest.dobuild ||
cur_input_frame.grid_index != latest.grid_index ||
!V2cmp(cur_input_frame.build, latest.build, 0.01f))
{
for (int i = 0; i < INPUT_BUFFER - 1; i++)
{
client_to_server.inputs[i + 1] = client_to_server.inputs[i];
}
cur_input_frame.tick = gs.tick;
client_to_server.inputs[0] = cur_input_frame;
}
// @BeforeShip figure out why tf the possess ship key is so unreliable
ENetPacket *packet = enet_packet_create((void *)&curmsg, sizeof(curmsg), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
enet_peer_send(peer, 0, packet);
static double last_input_sent_time = 0.0;
if (fabs(last_input_sent_time - time) > TIME_BETWEEN_INPUT_PACKETS)
{
ENetPacket *packet = enet_packet_create((void *)&client_to_server, sizeof(client_to_server), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
enet_peer_send(peer, 0, packet);
last_input_sent_time = time;
}
}
// @BeforeShip client side prediction and rollback to previous server authoritative state, then replay inputs
@ -434,9 +457,6 @@ static void frame(void)
}
}
set_color(RED);
sgp_draw_filled_rect(1.0f, 0.5f, 0.3f, 0.3f);
// gold target
set_color(GOLD);
sgp_draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1f, 0.1f);
@ -466,21 +486,28 @@ void event(const sapp_event *e)
switch (e->type)
{
case SAPP_EVENTTYPE_KEY_DOWN:
keydown[e->key_code] = true;
if (e->key_code == SAPP_KEYCODE_T)
{
mouse_frozen = !mouse_frozen;
}
if (keypressed[e->key_code].frame == 0)
if (!mouse_frozen)
{
keypressed[e->key_code].pressed = true;
keypressed[e->key_code].frame = e->frame_count;
keydown[e->key_code] = true;
if (keypressed[e->key_code].frame == 0)
{
keypressed[e->key_code].pressed = true;
keypressed[e->key_code].frame = e->frame_count;
}
}
break;
case SAPP_EVENTTYPE_KEY_UP:
keydown[e->key_code] = false;
keypressed[e->key_code].pressed = false;
keypressed[e->key_code].frame = 0;
if (!mouse_frozen)
{
keydown[e->key_code] = false;
keypressed[e->key_code].pressed = false;
keypressed[e->key_code].frame = 0;
}
break;
case SAPP_EVENTTYPE_MOUSE_DOWN:
if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT)

@ -56,7 +56,7 @@ void server(void *data)
ENetAddress address;
ENetHost *server;
int sethost = enet_address_set_host_ip(&address, LOCAL_SERVER_ADDRESS);
if(sethost != 0)
if (sethost != 0)
{
Log("Fishy return value from set host: %d\n", sethost);
}
@ -78,6 +78,7 @@ void server(void *data)
ENetEvent event;
uint64_t last_processed_time = stm_now();
float total_time = 0.0f;
uint64_t player_to_latest_tick_processed[MAX_PLAYERS] = {0};
while (true)
{
// @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this
@ -140,34 +141,34 @@ void server(void *data)
struct ClientToServer received = {0};
memcpy(&received, event.packet->data, length);
int64_t player_slot = (int64_t)event.peer->data;
uint64_t latest_tick = player_to_latest_tick_processed[player_slot];
// dobuild logging
if (false)
if (received.inputs[0].tick > latest_tick)
{
if (received.dobuild)
for (int i = INPUT_BUFFER - 1; i >= 0; i--)
{
Log("Received build command\n");
if (received.inputs[i].tick == 0) // empty input
continue;
if(received.inputs[i].tick <= latest_tick)
continue; // don't reprocess inputs already processed
struct InputFrame cur_input = received.inputs[i];
gs.players[player_slot].movement = cur_input.movement;
gs.players[player_slot].grid_index = cur_input.grid_index;
// for these "event" inputs, only modify the game state 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.inhabit)
{
gs.players[player_slot].inhabit = cur_input.inhabit;
}
if (cur_input.dobuild)
{
gs.players[player_slot].build = cur_input.build;
gs.players[player_slot].dobuild = cur_input.dobuild;
}
}
if (gs.players[player_slot].dobuild && !received.dobuild)
{
Log("Received end of build command\n");
}
}
gs.players[player_slot].movement = received.movement;
gs.players[player_slot].grid_index = received.grid_index;
// for these "event" inputs, only modify the game state 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 (received.inhabit)
{
gs.players[player_slot].inhabit = received.inhabit;
}
if (received.dobuild)
{
gs.players[player_slot].build = received.build;
gs.players[player_slot].dobuild = received.dobuild;
player_to_latest_tick_processed[player_slot] = received.inputs[0].tick;
}
}

@ -8,7 +8,9 @@
#define MAX_BOXES_PER_GRID 32
#define BOX_MASS 1.0f
#define TIMESTEP (1.0f / 60.0f) // not required to simulate at this, but this defines what tick the game is on
#define TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f)
#define SERVER_PORT 2551
#define INPUT_BUFFER 4
// must make this header and set the target address, just #define SERVER_ADDRESS "127.0.0.1"
#include "ipsettings.h" // don't leak IP!
@ -61,6 +63,7 @@ struct GameState
{
cpSpace *space;
uint64_t tick;
double time;
V2 goldpos;
@ -108,13 +111,17 @@ struct ServerToClient
struct ClientToServer
{
V2 movement;
bool inhabit;
struct InputFrame
{
uint64_t tick;
V2 movement;
bool inhabit;
// if grid_index != -1, this is in local coordinates to the grid
V2 build;
bool dobuild;
int grid_index;
// if grid_index != -1, this is in local coordinates to the grid
V2 build;
bool dobuild;
int grid_index;
} inputs[INPUT_BUFFER];
};
// server
@ -224,6 +231,11 @@ static V2 V2sub(V2 a, V2 b)
};
}
static bool V2cmp(V2 a, V2 b, float eps)
{
return V2length(V2sub(a, b)) < eps;
}
static inline float clamp01(float f)
{
return fmax(0.0f, fmin(f, 1.0f));

Loading…
Cancel
Save