From 13ccea184a328af7b020f4e70531c6c9e74ef0de Mon Sep 17 00:00:00 2001 From: xtreme8000 Date: Tue, 9 Jan 2024 23:51:22 +0100 Subject: [PATCH] Refactor ping code logic hopefully less bugs close #188 close #177 --- src/hud.c | 9 +- src/ping.c | 274 +++++++++++++++++++++++++++++++++-------------------- src/ping.h | 14 +-- 3 files changed, 184 insertions(+), 113 deletions(-) diff --git a/src/hud.c b/src/hud.c index 77084fd..b512128 100644 --- a/src/hud.c +++ b/src/hud.c @@ -2383,12 +2383,13 @@ static void hud_serverlist_render(mu_Context* ctx, float scalex, float scaley) { JSON_Array* servers = json_value_get_array(js); server_count = json_array_get_count(servers); + HashTable* pings = malloc(sizeof(struct HashTable)); + ht_setup(pings, sizeof(uint64_t), sizeof(struct ping_entry), 64); + pthread_mutex_lock(&serverlist_lock); serverlist = realloc(serverlist, server_count * sizeof(struct serverlist_entry)); CHECK_ALLOCATION_ERROR(serverlist) - ping_start(hud_serverlist_pingupdate); - player_count = 0; for(int k = 0; k < server_count; k++) { JSON_Object* s = json_array_get_object(servers, k); @@ -2410,7 +2411,7 @@ static void hud_serverlist_render(mu_Context* ctx, float scalex, float scaley) { int port; char ip[32]; if(network_identifier_split(serverlist[k].identifier, ip, &port)) - ping_check(ip, port, serverlist[k].identifier); + ping_check(pings, ip, port, serverlist[k].identifier); player_count += serverlist[k].current; } @@ -2418,6 +2419,8 @@ static void hud_serverlist_render(mu_Context* ctx, float scalex, float scaley) { qsort(serverlist, server_count, sizeof(struct serverlist_entry), hud_serverlist_sort); pthread_mutex_unlock(&serverlist_lock); + ping_start(pings, hud_serverlist_pingupdate); + http_release(request_serverlist); json_value_free(js); request_serverlist = NULL; diff --git a/src/ping.c b/src/ping.c index f81cde7..5bd5dd8 100644 --- a/src/ping.c +++ b/src/ping.c @@ -30,30 +30,101 @@ #include "list.h" #include "hud.h" #include "channel.h" -#include "hashtable.h" #include "utils.h" +#define IP_KEY(addr) (((uint64_t)addr.host << 16) | (addr.port)) + +struct ping_task { + HashTable* servers; + void (*ping_result)(void*, float time_delta, char* aos); + bool quit; +}; + struct channel ping_queue; +struct channel ping_lan_queue; ENetSocket sock, lan; pthread_t ping_thread; -void (*ping_result)(void*, float time_delta, char* aos); +pthread_t ping_lan_thread; -void ping_init() { - channel_create(&ping_queue, sizeof(struct ping_entry), 64); +static bool pings_retry(void* key, void* value, void* user) { + struct ping_entry* entry = (struct ping_entry*)value; + int* retry = (int*)user; - sock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); - enet_socket_set_option(sock, ENET_SOCKOPT_NONBLOCK, 1); + enet_socket_send(sock, &entry->addr, &(ENetBuffer) {.data = "HELLO", .dataLength = 5}, 1); + entry->time_start = window_time(); - lan = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); - enet_socket_set_option(lan, ENET_SOCKOPT_NONBLOCK, 1); - enet_socket_set_option(lan, ENET_SOCKOPT_BROADCAST, 1); + if(*retry > 0) + log_warn("Ping timeout on %s, retrying (%i)", entry->aos, *retry); - pthread_create(&ping_thread, NULL, ping_update, NULL); + return false; } -void ping_deinit() { +void* ping_update(void* data) { + pthread_detach(pthread_self()); + + ENetAddress from; + uint8_t tmp[512]; + + ENetBuffer buf = { + .data = tmp, + .dataLength = sizeof(tmp), + }; + + while(1) { + struct ping_task task; + channel_await(&ping_queue, &task); + + if(task.quit) + break; + + ht_iterate_remove(task.servers, (int[]) {0}, pings_retry); + + for(int try = 0; try < 3; try++) { + float ping_start = window_time(); + + while(1) { + int timeout = (2.5F - (window_time() - ping_start)) * 1000; + + if(timeout <= 0) + break; + + enet_uint32 event = ENET_SOCKET_WAIT_RECEIVE; + enet_socket_wait(sock, &event, timeout); + size_t recv_length = enet_socket_receive(sock, &from, &buf, 1); + uint64_t ID = IP_KEY(from); + + if(recv_length != 0) { + struct ping_entry* entry = ht_lookup(task.servers, &ID); + + if(entry) { + if(recv_length > 0) { // received something! + if(!strncmp(buf.data, "HI", recv_length)) { + task.ping_result(NULL, window_time() - entry->time_start, entry->aos); + ht_erase(task.servers, &ID); + } + } else { // connection was closed + ht_erase(task.servers, &ID); + } + } + } + } + + ht_iterate_remove(task.servers, (int[]) {try}, pings_retry); + + if(ht_is_empty(task.servers)) + break; + } + + if(!ht_is_empty(task.servers)) + log_warn("Some servers did not reply to ping requests"); + + ht_destroy(task.servers); + free(task.servers); + } + enet_socket_destroy(sock); - enet_socket_destroy(lan); + + return NULL; } static void ping_lan() { @@ -68,132 +139,129 @@ static void ping_lan() { enet_socket_send(lan, &addr, &buffer, 1); } -static bool pings_retry(void* key, void* value, void* user) { - struct ping_entry* entry = (struct ping_entry*)value; - - if(window_time() - entry->time_start > 2.0F) { // timeout - // try up to 3 times after first failed attempt - if(entry->trycount >= 3) { - return true; - } else { - enet_socket_send(sock, &entry->addr, &(ENetBuffer) {.data = "HELLO", .dataLength = 5}, 1); - entry->time_start = window_time(); - entry->trycount++; - log_warn("Ping timeout on %s, retrying", entry->aos); - } - } - - return false; -} - -#define IP_KEY(addr) (((uint64_t)addr.host << 16) | (addr.port)); - -void* ping_update(void* data) { +void* ping_lan_update(void* data) { pthread_detach(pthread_self()); - ping_lan(); - float ping_start = window_time(); + ENetAddress from; + uint8_t tmp[512]; - HashTable pings; - ht_setup(&pings, sizeof(uint64_t), sizeof(struct ping_entry), 64); + ENetBuffer buf = { + .data = tmp, + .dataLength = sizeof(tmp), + }; while(1) { - size_t drain = channel_size(&ping_queue); - for(size_t k = 0; (k < drain) || (!pings.size && window_time() - ping_start >= 8.0F); k++) { - struct ping_entry entry; - channel_await(&ping_queue, &entry); + struct ping_task task; + channel_await(&ping_lan_queue, &task); - uint64_t ID = IP_KEY(entry.addr); - ht_insert(&pings, &ID, &entry); - } + if(task.quit) + break; + + float ping_start = window_time(); - char tmp[512]; - ENetAddress from; + ping_lan(); - ENetBuffer buf = { - .data = tmp, - .dataLength = sizeof(tmp), - }; + bool found = false; while(1) { - int recvLength = enet_socket_receive(sock, &from, &buf, 1); - uint64_t ID = IP_KEY(from); - - if(recvLength != 0) { - struct ping_entry* entry = ht_lookup(&pings, &ID); - - if(entry) { - if(recvLength > 0) { // received something! - if(!strncmp(buf.data, "HI", recvLength)) { - ping_result(NULL, window_time() - entry->time_start, entry->aos); - ht_erase(&pings, &ID); - } else { - entry->trycount++; - } - } else { // connection was closed - ht_erase(&pings, &ID); - } - } - } else { // would block + int timeout = (8.0F - (window_time() - ping_start)) * 1000; + + if(timeout <= 0) break; - } - } - ht_iterate_remove(&pings, NULL, pings_retry); + enet_uint32 event = ENET_SOCKET_WAIT_RECEIVE; + enet_socket_wait(sock, &event, timeout); + + int length = enet_socket_receive(lan, &from, &buf, 1); - int length = enet_socket_receive(lan, &from, &buf, 1); - if(length) { - JSON_Value* js = json_parse_string(buf.data); - if(js) { - JSON_Object* root = json_value_get_object(js); + if(length) { + JSON_Value* js = json_parse_string(buf.data); - struct serverlist_entry e; + if(js) { + JSON_Object* root = json_value_get_object(js); - strcpy(e.country, "LAN"); - e.ping = ceil((window_time() - ping_start) * 1000.0F); - snprintf(e.identifier, sizeof(e.identifier) - 1, "aos://%u:%u", from.host, from.port); + struct serverlist_entry e; - strncpy(e.name, json_object_get_string(root, "name"), sizeof(e.name) - 1); - e.name[sizeof(e.name) - 1] = 0; - strncpy(e.gamemode, json_object_get_string(root, "game_mode"), sizeof(e.gamemode) - 1); - e.gamemode[sizeof(e.gamemode) - 1] = 0; - strncpy(e.map, json_object_get_string(root, "map"), sizeof(e.map) - 1); - e.map[sizeof(e.map) - 1] = 0; - e.current = json_object_get_number(root, "players_current"); - e.max = json_object_get_number(root, "players_max"); - ping_result(&e, window_time() - ping_start, NULL); + strcpy(e.country, "LAN"); + e.ping = ceil((window_time() - ping_start) * 1000.0F); + snprintf(e.identifier, sizeof(e.identifier) - 1, "aos://%u:%u", from.host, from.port); - json_value_free(js); + strncpy(e.name, json_object_get_string(root, "name"), sizeof(e.name) - 1); + e.name[sizeof(e.name) - 1] = 0; + strncpy(e.gamemode, json_object_get_string(root, "game_mode"), sizeof(e.gamemode) - 1); + e.gamemode[sizeof(e.gamemode) - 1] = 0; + strncpy(e.map, json_object_get_string(root, "map"), sizeof(e.map) - 1); + e.map[sizeof(e.map) - 1] = 0; + e.current = json_object_get_number(root, "players_current"); + e.max = json_object_get_number(root, "players_max"); + task.ping_result(&e, window_time() - ping_start, NULL); + found = true; + + json_value_free(js); + } } } - usleep(1000); + if(!found) + log_info("No local servers found"); } + + enet_socket_destroy(lan); + + return NULL; } -void ping_check(char* addr, int port, char* aos) { - struct ping_entry entry = { - .trycount = 0, - .addr.port = port, - .time_start = window_time(), - }; +void ping_check(HashTable* pings, char* addr, int port, char* aos) { + struct ping_entry entry; strncpy(entry.aos, aos, sizeof(entry.aos) - 1); entry.aos[sizeof(entry.aos) - 1] = 0; enet_address_set_host(&entry.addr, addr); + entry.addr.port = port; - channel_put(&ping_queue, &entry); - - enet_socket_send(sock, &entry.addr, &(ENetBuffer) {.data = "HELLO", .dataLength = 5}, 1); + ht_insert(pings, (uint64_t[]) {IP_KEY(entry.addr)}, &entry); } -void ping_start(void (*result)(void*, float, char*)) { +void ping_start(HashTable* pings, void (*result)(void*, float, char*)) { ping_stop(); - ping_result = result; + channel_put(&ping_queue, + &(struct ping_task) { + .servers = pings, + .ping_result = result, + .quit = false, + }); + + channel_put(&ping_lan_queue, + &(struct ping_task) { + .ping_result = result, + .quit = false, + }); } void ping_stop() { channel_clear(&ping_queue); + channel_clear(&ping_lan_queue); +} + +void ping_init() { + channel_create(&ping_queue, sizeof(struct ping_task), 4); + channel_create(&ping_lan_queue, sizeof(struct ping_task), 4); + + sock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); + enet_socket_set_option(sock, ENET_SOCKOPT_NONBLOCK, 1); + + lan = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); + enet_socket_set_option(lan, ENET_SOCKOPT_NONBLOCK, 1); + enet_socket_set_option(lan, ENET_SOCKOPT_BROADCAST, 1); + + pthread_create(&ping_thread, NULL, ping_update, NULL); + pthread_create(&ping_lan_thread, NULL, ping_lan_update, NULL); +} + +void ping_deinit() { + ping_stop(); + channel_put(&ping_queue, &(struct ping_task) {.quit = true}); + channel_put(&ping_lan_queue, &(struct ping_task) {.quit = true}); } diff --git a/src/ping.h b/src/ping.h index fb905b6..262ba4f 100644 --- a/src/ping.h +++ b/src/ping.h @@ -22,18 +22,18 @@ #include +#include "hashtable.h" + struct ping_entry { ENetAddress addr; char aos[64]; float time_start; - int trycount; }; -void ping_init(); -void ping_deinit(); -void ping_check(char* addr, int port, char* aos); -void* ping_update(void* data); -void ping_start(void (*result)(void*, float, char*)); -void ping_stop(); +void ping_init(void); +void ping_deinit(void); +void ping_check(HashTable* pings, char* addr, int port, char* aos); +void ping_start(HashTable* pings, void (*result)(void*, float, char*)); +void ping_stop(void); #endif