diff --git a/build_debug_dll.bat b/build_debug_dll.bat new file mode 100644 index 0000000..3503889 --- /dev/null +++ b/build_debug_dll.bat @@ -0,0 +1,4 @@ +@REM for whitebox +call shadergen.bat +set compileopts=/Zi /DDEBUG /DLL /OUT:flight_dll /LD +call build_msvc.bat \ No newline at end of file diff --git a/buildsettings.h b/buildsettings.h index dbaa2fb..1473356 100644 --- a/buildsettings.h +++ b/buildsettings.h @@ -16,7 +16,7 @@ // #define PROFILING // Intensive profiling means profiling a lot of little tiny stuff. Not always enabled because tanks performance -#define INTENSIVE_PROFILING +// #define INTENSIVE_PROFILING // #define DEBUG_RENDERING #define DEBUG_WORLD // #define UNLOCK_ALL diff --git a/flight.rdbg b/flight.rdbg index d36c7a0..8a56a64 100644 Binary files a/flight.rdbg and b/flight.rdbg differ diff --git a/input_go_down.bin b/input_go_down.bin new file mode 100644 index 0000000..a7f526b Binary files /dev/null and b/input_go_down.bin differ diff --git a/input_go_up.bin b/input_go_up.bin new file mode 100644 index 0000000..18f3940 Binary files /dev/null and b/input_go_up.bin differ diff --git a/inputtest.bat b/inputtest.bat new file mode 100644 index 0000000..1a718ef --- /dev/null +++ b/inputtest.bat @@ -0,0 +1,4 @@ +set EXECUTABLE=x64\Debug\Flight.exe + +START /b %EXECUTABLE% host=true replay_inputs_from=input_go_up.bin +%EXECUTABLE% replay_inputs_from=input_go_down.bin \ No newline at end of file diff --git a/main.c b/main.c index 7ff8a9d..808d4ff 100644 --- a/main.c +++ b/main.c @@ -60,6 +60,7 @@ typedef struct KeyPressed static KeyPressed keypressed[MAX_KEYDOWN] = {0}; static cpVect mouse_pos = {0}; static FILE *record_inputs_to = NULL; +static FILE *replay_inputs_from = NULL; static bool fullscreened = false; static bool picking_new_boxtype = false; static double exec_time = 0.0; // cosmetic bouncing, network stats @@ -269,6 +270,16 @@ struct SquadMeta squad_meta(enum Squad squad) return (struct SquadMeta){0}; } +static size_t serialized_inputframe_length() +{ + InputFrame frame = {0}; + unsigned char serialized[1024 * 5] = {0}; + SerState ser = init_serializing(&gs, serialized, ARRLEN(serialized), NULL, false); + ser_inputframe(&ser, &frame); + flight_assert(ser_size(&ser) > 1); + return ser_size(&ser); +} + static enum BoxType currently_building() { flight_assert(cur_toolbar_slot >= 0); @@ -514,7 +525,8 @@ static void init(void) printf( "Usage: astris.exe [option]=data , the =stuff is required\n" "host - hosts a server locally if exists in commandline, like `astris.exe host=yes`\n" - "record_inputs_to - records inputs to the file specified"); + "record_inputs_to - records inputs to the file specified\n" + "replay_inputs_from - replays inputs from the file specified\n"); if (sargs_exists("host")) { server_thread_handle = (void *)_beginthread(server, 0, (void *)&server_info); @@ -523,16 +535,32 @@ static void init(void) if (sargs_exists("record_inputs_to")) { const char *filename = sargs_value("record_inputs_to"); + Log("Recording inputs to %s\n", filename); if (filename == NULL) { quit_with_popup("Failed to record inputs, filename not specified", "Failed to record inputs"); } - fopen_s(&record_inputs_to, filename, "wb"); + errno_t error = fopen_s(&record_inputs_to, filename, "wb"); + Log("%d\n", error); if (record_inputs_to == NULL) { quit_with_popup("Failed to open file to record inputs into", "Failed to record inputs"); } } + if (sargs_exists("replay_inputs_from")) + { + const char *filename = sargs_value("replay_inputs_from"); + Log("Replaying inputs from %s\n", filename); + if (filename == NULL) + { + quit_with_popup("Failed to replay inputs, filename not specified", "Failed to replay inputs"); + } + fopen_s(&replay_inputs_from, filename, "rb"); + if (replay_inputs_from == NULL) + { + quit_with_popup("Failed to open file to replay inputs from", "Failed to replay inputs"); + } + } } // audio @@ -1634,11 +1662,8 @@ static void frame(void) if (applied_gamestate_packet) { uint64_t server_current_tick = tick(&gs); - int ticks_should_repredict = (int)predicted_to_tick - (int)server_current_tick; - int healthy_num_ticks_ahead = (int)ceil((((double)peer->roundTripTime + (double)peer->roundTripTimeVariance * CAUTIOUS_MULTIPLIER) / 1000.0) / TIMESTEP) + 6; - int ticks_to_repredict = ticks_should_repredict; if (ticks_should_repredict < healthy_num_ticks_ahead - 1) @@ -1746,8 +1771,7 @@ static void frame(void) } // Create and send input packet, and predict a frame of gamestate - static InputFrame cur_input_frame = { - 0}; // keep across frames for high refresh rate screens + static InputFrame cur_input_frame = {0}; // keep across frames for high refresh rate screens static size_t last_input_committed_tick = 0; { // prepare the current input frame, such that when processed next, @@ -1814,10 +1838,36 @@ static void frame(void) do { // "commit" the input. each input must be on a successive tick. - if (tick(&gs) > last_input_committed_tick) + // if (tick(&gs) > last_input_committed_tick) + while(tick(&gs) > last_input_committed_tick) { - cur_input_frame.tick = tick(&gs); - last_input_committed_tick = tick(&gs); + if (replay_inputs_from != NULL) + { + unsigned char deserialized[2048] = {0}; + flight_assert(ARRLEN(deserialized) >= serialized_inputframe_length()); + + size_t bytes_read = fread(deserialized, 1, serialized_inputframe_length(), replay_inputs_from); + if (bytes_read != serialized_inputframe_length()) + { + // no more inputs in the saved file + flight_assert(myentity() != NULL); + Entity *should_be_medbay = get_entity(&gs, myentity()->currently_inside_of_box); + flight_assert(should_be_medbay != NULL); + flight_assert(should_be_medbay->is_box && should_be_medbay->box_type == BoxMedbay); + exit(0); + } + SerState ser = init_deserializing(&gs, deserialized, serialized_inputframe_length(), false); + SerMaybeFailure maybe_fail = ser_inputframe(&ser, &cur_input_frame); + flight_assert(!maybe_fail.failed); + flight_assert(serialized_inputframe_length() == ser_size(&ser)); + } + else + { + cur_input_frame.tick = last_input_committed_tick + 1; + // cur_input_frame.tick = tick(&gs); + } + + last_input_committed_tick = cur_input_frame.tick; InputFrame *to_push_to = queue_push_element(&input_queue); if (to_push_to == NULL) @@ -1830,8 +1880,40 @@ static void frame(void) *to_push_to = cur_input_frame; + /* + optionally save the current input frame for debugging purposes + ░░░░░▄▄▄▄▀▀▀▀▀▀▀▀▄▄▄▄▄▄░░░░░░░ + ░░░░░█░░░░▒▒▒▒▒▒▒▒▒▒▒▒░░▀▀▄░░░░ + ░░░░█░░░▒▒▒▒▒▒░░░░░░░░▒▒▒░░█░░░ + ░░░█░░░░░░▄██▀▄▄░░░░░▄▄▄░░░░█░░ + ░▄▀▒▄▄▄▒░█▀▀▀▀▄▄█░░░██▄▄█░░░░█░ + █░▒█▒▄░▀▄▄▄▀░░░░░░░░█░░░▒▒▒▒▒░█ + █░▒█░█▀▄▄░░░░░█▀░░░░▀▄░░▄▀▀▀▄▒█ + ░█░▀▄░█▄░█▀▄▄░▀░▀▀░▄▄▀░░░░█░░█░ + ░░█░░░▀▄▀█▄▄░█▀▀▀▄▄▄▄▀▀█▀██░█░░ + ░░░█░░░░██░░▀█▄▄▄█▄▄█▄████░█░░░ + ░░░░█░░░░▀▀▄░█░░░█░█▀██████░█░░ + ░░░░░▀▄░░░░░▀▀▄▄▄█▄█▄█▄█▄▀░░█░░ + ░░░░░░░▀▄▄░▒▒▒▒░░░░░░░░░░▒░░░█░ + ░░░░░░░░░░▀▀▄▄░▒▒▒▒▒▒▒▒▒▒░░░░█░ + ░░░░░░░░░░░░░░▀▄▄▄▄▄░░░░░░░░█░░ + le informative comment + */ + if (record_inputs_to != NULL) + { + unsigned char serialized[2048] = {0}; + SerState ser = init_serializing(&gs, serialized, ARRLEN(serialized), NULL, false); + SerMaybeFailure maybe_fail = ser_inputframe(&ser, &cur_input_frame); + flight_assert(!maybe_fail.failed); + flight_assert(serialized_inputframe_length() == ser_size(&ser)); + size_t written = fwrite(serialized, 1, ser_size(&ser), record_inputs_to); + flight_assert(written == ser_size(&ser)); + } + if (myplayer() != NULL) + { myplayer()->input = cur_input_frame; // for the client side prediction! + } cur_input_frame = (InputFrame){0}; cur_input_frame.take_over_squad = -1; // @Robust make this zero initialized @@ -2456,7 +2538,13 @@ static void frame(void) void cleanup(void) { sargs_shutdown(); + fclose(log_file); + if (record_inputs_to != NULL) + fclose(record_inputs_to); + if (replay_inputs_from != NULL) + fclose(replay_inputs_from); + sg_destroy_pipeline(hueshift_pipeline); ma_mutex_lock(&server_info.info_mutex); diff --git a/server.c b/server.c index 6cddb16..e25e22a 100644 --- a/server.c +++ b/server.c @@ -225,8 +225,11 @@ void server(void *info_raw) if (get_entity(&gs, gs.players[player_slot].entity) == NULL) buffer_to_fill = &throwaway_buffer; - queue_clear(&player_input_queues[player_slot]); - struct ClientToServer received = {.mic_data = buffer_to_fill, .input_data = &player_input_queues[player_slot]}; + Queue new_inputs = {0}; + char new_inputs_data[QUEUE_SIZE_FOR_ELEMENTS(sizeof(InputFrame), INPUT_QUEUE_MAX)] = {0}; + queue_init(&new_inputs, sizeof(InputFrame), new_inputs_data, ARRLEN(new_inputs_data)); + + struct ClientToServer received = {.mic_data = buffer_to_fill, .input_data = &new_inputs}; unsigned char decompressed[MAX_CLIENT_TO_SERVER] = {0}; size_t decompressed_max_len = MAX_CLIENT_TO_SERVER; flight_assert(LZO1X_MEM_DECOMPRESS == 0); @@ -241,6 +244,26 @@ void server(void *info_raw) { Log("Bad packet from client %d | %d %s\n", (int)player_slot, maybe_fail.line, maybe_fail.expression); } + else + { + QUEUE_ITER(&new_inputs, InputFrame, new_input) + { + QUEUE_ITER(&player_input_queues[player_slot], InputFrame, existing_input) + { + if (existing_input->tick == new_input->tick && existing_input->been_processed) + { + new_input->been_processed = true; + } + } + } + queue_clear(&player_input_queues[player_slot]); + QUEUE_ITER(&new_inputs, InputFrame, cur) + { + InputFrame *new_elem = queue_push_element(&player_input_queues[player_slot]); + flight_assert(new_elem != NULL); + *new_elem = *cur; + } + } } else { @@ -291,16 +314,21 @@ void server(void *info_raw) { PROFILE_SCOPE("World Processing") { - CONNECTED_PEERS(enet_host, cur) + CONNECTED_PEERS(enet_host, cur_peer) { - int this_player_index = (int)(int64_t)cur->data; + int this_player_index = (int)(int64_t)cur_peer->data; QUEUE_ITER(&player_input_queues[this_player_index], InputFrame, cur) { if (cur->tick == tick(&gs)) { gs.players[this_player_index].input = *cur; + cur->been_processed = true; break; } + if (cur->tick < tick(&gs) && !cur->been_processed) + { + Log("Did not process input from client %d %llu ticks ago!\n", this_player_index,tick(&gs) - cur->tick); + } } } diff --git a/types.h b/types.h index e63df02..9754655 100644 --- a/types.h +++ b/types.h @@ -232,6 +232,9 @@ enum ScannerPointKind typedef struct InputFrame { uint64_t tick; + + bool been_processed; // not serialized, used by server just to keep track of what inputs have been processed + cpVect movement; double rotation; @@ -511,6 +514,7 @@ SerState init_serializing(GameState *gs, unsigned char *bytes, size_t max_size, SerState init_deserializing(GameState *gs, unsigned char *bytes, size_t max_size, bool from_disk); SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s); SerMaybeFailure ser_client_to_server(SerState *ser, ClientToServer *msg); +SerMaybeFailure ser_inputframe(SerState *ser, InputFrame *i); // entities bool is_burning(Entity *missile);