diff --git a/src/instance/game_instance.c b/src/instance/game_instance.c index cd7917d..dd84832 100644 --- a/src/instance/game_instance.c +++ b/src/instance/game_instance.c @@ -5,7 +5,7 @@ #define INPUT_QUEUE_CAPACITY 8 void game_instance_init(Game_Instance *instance) { - instance->local_player_index = 0xFFFF; + instance->local_player_id = 0xFFFF; // TODO: SS - Init input-queue. instance->simulation_accumulator = 0.0f; // TODO: SS - Should probably be moved to the Ingame context. instance->game_session = NULL; @@ -38,19 +38,25 @@ bool game_instance_host_session(Game_Instance *instance, const Game_Session_Sett return false; } - if(!networking_try_host(&instance->game_networking, instance->game_session, settings)) { + uint16_t host_player_id = 0xFFFF; + if(!networking_try_host(&instance->game_networking, instance->game_session, settings, &host_player_id)) { printf("Failed to host session.\n"); free(instance->game_session); instance->game_session = NULL; return false; } + + instance->local_player_id = host_player_id; } else { game_session_init(instance->game_session, settings); + + for(uint32_t i = 0; i < settings.max_players; i++) { + // When playing locally, create the player(s) here. + assert(game_session_create_player(instance->game_session, &instance->local_player_id)); // TODO: SS - Fix me! Always assigning the newest player to be the active 'local_player_id' which isn't correct. + } } - - assert(game_session_create_player(instance->game_session, &instance->local_player_index)); // Create the host (you!). - + return true; } diff --git a/src/instance/game_instance.h b/src/instance/game_instance.h index 22cdcfc..1b6b456 100644 --- a/src/instance/game_instance.h +++ b/src/instance/game_instance.h @@ -9,7 +9,7 @@ #include "shared/squeue.h" typedef struct { - uint16_t local_player_index; + uint16_t local_player_id; SQueue local_input_queue; double simulation_accumulator; diff --git a/src/presentation/states/state_ingame.c b/src/presentation/states/state_ingame.c index 79b534f..474706e 100644 --- a/src/presentation/states/state_ingame.c +++ b/src/presentation/states/state_ingame.c @@ -77,7 +77,7 @@ static void state_tick(Presentation_State *state) { if(game_instance_pop_local_input(instance, &input)) { // TODO: SS - We should probably check if this input is invalid for the snake. (pressing "go right" when going left, for example.) // If it is invalid, the next input in the queue should be tried. - game_session_set_player_input(session, instance->local_player_index, input); + game_session_set_player_input(session, instance->local_player_id, input); } game_session_tick(session); @@ -299,7 +299,7 @@ static void state_render(Presentation_State *state) { sim_world->game_world->seed, sim_world->game_world->grid.width, sim_world->game_world->grid.height, session->settings.tickrate, - instance->local_player_index, instance->local_player_index == 0 ? "true" : "false", // NOTE: SS - You're the host if you're player 0. + instance->local_player_id, instance->local_player_id == 0 ? "true" : "false", // NOTE: SS - You're the host if you're player 0. game_session_get_amount_of_active_players(session), sim_world->max_players ); diff --git a/src/session/game_session.c b/src/session/game_session.c index 051dbe4..7447417 100644 --- a/src/session/game_session.c +++ b/src/session/game_session.c @@ -138,7 +138,7 @@ bool game_session_create_player(Game_Session *session, uint16_t *out_player_inde void game_session_destroy_player(Game_Session *session, uint16_t player_index) { assert(session != NULL); assert(player_index < session->settings.max_players); - + Game_Session_Player *player = &session->players[player_index]; assert(player != NULL); assert(player->active); diff --git a/src/session/networking.c b/src/session/networking.c index a478d14..466ac5a 100644 --- a/src/session/networking.c +++ b/src/session/networking.c @@ -6,15 +6,62 @@ static void listen_callback( const char *event, /* "joined", "leaved", "game" */ - int64_t messageId, /* sekventiellt meddelande‑ID (från host) */ - const char *clientId, /* avsändarens klient‑ID (eller NULL) */ + int64_t messsage_id, /* sekventiellt meddelande‑ID (från host) */ + const char *client_id, /* avsändarens klient‑ID (eller NULL) */ json_t *data, /* JSON‑objekt med godtycklig speldata */ void *context /* godtycklig pekare som skickas vidare */ ) { - printf("#%li :: Event: '%s' from '%s'.\n", messageId, event, clientId); - Game_Networking *networking = (Game_Networking *)context; + assert(networking != NULL); + assert(networking->game_session != NULL); + (void)data; + + printf("#%li :: Event: '%s' from '%s'.\n", messsage_id, event, client_id); + + assert(networking != NULL); + + if(strcmp(event, "joined") == 0) { + // Create a new player in the session, get the id and store it as the value in the + // 'client_id_to_player_index' hashmap using 'client_id' as the key. + + uint32_t *existing_id = map_get(&networking->client_id_to_player_index, client_id); + assert(existing_id == NULL); // Something has gone terribly wrong if we get a 'joined' event for the same client-id with them still being in the map. + + uint16_t player_index = 0xFFFF; + assert(game_session_create_player(networking->game_session, &player_index)); + assert(map_set(&networking->client_id_to_player_index, client_id, player_index) == 0); // 0 = OK. + + // TODO: SS - Send a 'game' message to the joinee with information about the session? + + printf("Added client-id '%s' (player-index: %u) to the map.\n", client_id, player_index); + } + else if(strcmp(event, "leaved") == 0) { + // TODO: SS - Index the hashmap using 'client_id' as the key and attempt to get the + // player-index for that client. Run 'game_session_destroy_player()'. + + uint32_t *existing_id = map_get(&networking->client_id_to_player_index, client_id); + assert(existing_id != NULL); // Something has gone terribly wrong if we get a 'leaved' event for the a client-id that isn't in the map. + + uint16_t player_id_to_remove = *existing_id; + + game_session_destroy_player(networking->game_session, player_id_to_remove); // Returns void but crashes internally if id isn't OK. + map_remove(&networking->client_id_to_player_index, client_id); + + printf("Removed client-id '%s' (player-index: %u) from the map.\n", client_id, player_id_to_remove); + } + else if(strcmp(event, "game") == 0) { + // TODO: SS - Check the 'data' to see what type of game-event it is. + // Possible game-events I can think of right now: + // - Player-input. + // - Match-state changed. + // - Chat-message? + // - Session-state? + // - Maybe Player-Joined/Left. (seems like mp_api only sends 'joined'/'leaved' events to the host.) + } + else { + printf("UNKNOWN EVENT-TYPE: '%s'.\n", event); + } } static inline bool setup_api(Game_Networking *networking) { @@ -52,13 +99,14 @@ static bool start_listening(Game_Networking *networking) { static bool stop_listening(Game_Networking *networking) { assert(networking != NULL); assert(networking->api != NULL); - assert(networking->listener_id >= 0); mp_api_unlisten(networking->api, (int)networking->listener_id); + return true; } -bool networking_try_host(Game_Networking *networking, Game_Session *session, Game_Session_Settings settings) { +bool networking_try_host(Game_Networking *networking, Game_Session *session, Game_Session_Settings settings, uint16_t *out_host_player_id) { assert(networking != NULL); + assert(session != NULL); if(!settings.online) { printf("Failed to host; Game_Session_Settings.online == false, expected it to be true.\n"); @@ -87,9 +135,12 @@ bool networking_try_host(Game_Networking *networking, Game_Session *session, Gam } printf("Started hosting session '%s', local_client_id is '%s'.\n", session_id, local_client_id); + networking->game_session = session; networking->session_id = session_id; networking->local_client_id = local_client_id; + map_init(&networking->client_id_to_player_index); + assert(start_listening(networking)); // Set up session. @@ -97,6 +148,13 @@ bool networking_try_host(Game_Networking *networking, Game_Session *session, Gam session, settings ); + + // Add the host to the game. + uint16_t host_player_index = 0xFFFF; + assert(game_session_create_player(networking->game_session, &host_player_index)); + map_set(&networking->client_id_to_player_index, local_client_id, host_player_index); + *out_host_player_id = host_player_index; + printf("Host-player created (client-id: '%s'). Player-id: %u.\n", local_client_id, host_player_index); if(response_data != NULL) { // TODO: SS - Handle JSON-response. @@ -108,6 +166,7 @@ bool networking_try_host(Game_Networking *networking, Game_Session *session, Gam bool networking_try_join(Game_Networking *networking, Game_Session *session, const char *session_id) { assert(networking != NULL); + assert(session != NULL); if(strlen(session_id) == 0) { return false; @@ -127,7 +186,7 @@ bool networking_try_join(Game_Networking *networking, Game_Session *session, con networking->api, // API. session_id, // Session-code. NULL, // JSON-data to send. // TODO: SS - Send information about you as a player (username, ..). - &result_session_id, // Received Session-id. I assume it should be the same as 'session_id'. + &result_session_id, // Received Session-id. I assume it should be the same as 'session_id'. // TODO: SS - Ask Robin why mp_api_join() returns a session_id. Is it to support 'abcdef' => 'ABCDEF' and/or proxies? &local_client_id, // Received client-id. &received_json // Received JSON-data. ); @@ -136,14 +195,17 @@ bool networking_try_join(Game_Networking *networking, Game_Session *session, con printf("Failed to join; Got result: %i.\n", join_result); return false; } - assert(strcmp(result_session_id, session_id) == 0); // TODO: SS - Ask Robin why mp_api_join() returns a session_id. Is it to support 'abcdef' => 'ABCDEF'? + assert(strcmp(result_session_id, session_id) == 0); printf("Joined session '%s', local_client_id is '%s'.\n", result_session_id, local_client_id); + networking->game_session = session; networking->session_id = result_session_id; networking->local_client_id = local_client_id; + map_init(&networking->client_id_to_player_index); + // TODO: SS - Decide how to network this.. - // Should we just receive the session's settings as well as a long list of commands of what has happened throughout the match - at least one per tick? + // Should we just receive the session's settings as well as a long list of commands of what has happened throughout the match (so far) - at least one per tick? // Or, should this client receive the current state of the session? if(received_json != NULL) { @@ -170,10 +232,13 @@ bool networking_stop_hosting(Game_Networking *networking) { return false; } + map_deinit(&networking->client_id_to_player_index); + assert(stop_listening(networking)); mp_api_destroy(networking->api); networking->api = NULL; + networking->session = NULL; if(networking->session_id != NULL) { free(networking->session_id); diff --git a/src/session/networking.h b/src/session/networking.h index 7b77322..d2db014 100644 --- a/src/session/networking.h +++ b/src/session/networking.h @@ -4,6 +4,8 @@ #include "multiplayer_api.h" #include "game_session.h" +#include "shared/map.h" + typedef struct { MultiplayerApi *api; @@ -11,9 +13,12 @@ typedef struct { char *local_client_id; uint32_t listener_id; + + Game_Session *game_session; // Comes from Game_Instance. + map_uint_t client_id_to_player_index; } Game_Networking; -bool networking_try_host(Game_Networking *networking, Game_Session *session, Game_Session_Settings settings); +bool networking_try_host(Game_Networking *networking, Game_Session *session, Game_Session_Settings settings, uint16_t *out_host_player_id); bool networking_try_join(Game_Networking *networking, Game_Session *session, const char *session_id); bool networking_stop_hosting(Game_Networking *networking); diff --git a/src/shared/map.c b/src/shared/map.c new file mode 100644 index 0000000..341f757 --- /dev/null +++ b/src/shared/map.c @@ -0,0 +1,193 @@ +/** + * Copyright (c) 2014 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include "map.h" + +struct map_node_t { + unsigned hash; + void *value; + map_node_t *next; + /* char key[]; */ + /* char value[]; */ +}; + + +static unsigned map_hash(const char *str) { + unsigned hash = 5381; + while (*str) { + hash = ((hash << 5) + hash) ^ *str++; + } + return hash; +} + + +static map_node_t *map_newnode(const char *key, void *value, int vsize) { + map_node_t *node; + int ksize = strlen(key) + 1; + int voffset = ksize + ((sizeof(void*) - ksize) % sizeof(void*)); + node = malloc(sizeof(*node) + voffset + vsize); + if (!node) return NULL; + memcpy(node + 1, key, ksize); + node->hash = map_hash(key); + node->value = ((char*) (node + 1)) + voffset; + memcpy(node->value, value, vsize); + return node; +} + + +static int map_bucketidx(map_base_t *m, unsigned hash) { + /* If the implementation is changed to allow a non-power-of-2 bucket count, + * the line below should be changed to use mod instead of AND */ + return hash & (m->nbuckets - 1); +} + + +static void map_addnode(map_base_t *m, map_node_t *node) { + int n = map_bucketidx(m, node->hash); + node->next = m->buckets[n]; + m->buckets[n] = node; +} + + +static int map_resize(map_base_t *m, int nbuckets) { + map_node_t *nodes, *node, *next; + map_node_t **buckets; + int i; + /* Chain all nodes together */ + nodes = NULL; + i = m->nbuckets; + while (i--) { + node = (m->buckets)[i]; + while (node) { + next = node->next; + node->next = nodes; + nodes = node; + node = next; + } + } + /* Reset buckets */ + buckets = realloc(m->buckets, sizeof(*m->buckets) * nbuckets); + if (buckets != NULL) { + m->buckets = buckets; + m->nbuckets = nbuckets; + } + if (m->buckets) { + memset(m->buckets, 0, sizeof(*m->buckets) * m->nbuckets); + /* Re-add nodes to buckets */ + node = nodes; + while (node) { + next = node->next; + map_addnode(m, node); + node = next; + } + } + /* Return error code if realloc() failed */ + return (buckets == NULL) ? -1 : 0; +} + + +static map_node_t **map_getref(map_base_t *m, const char *key) { + unsigned hash = map_hash(key); + map_node_t **next; + if (m->nbuckets > 0) { + next = &m->buckets[map_bucketidx(m, hash)]; + while (*next) { + if ((*next)->hash == hash && !strcmp((char*) (*next + 1), key)) { + return next; + } + next = &(*next)->next; + } + } + return NULL; +} + + +void map_deinit_(map_base_t *m) { + map_node_t *next, *node; + int i; + i = m->nbuckets; + while (i--) { + node = m->buckets[i]; + while (node) { + next = node->next; + free(node); + node = next; + } + } + free(m->buckets); +} + + +void *map_get_(map_base_t *m, const char *key) { + map_node_t **next = map_getref(m, key); + return next ? (*next)->value : NULL; +} + + +int map_set_(map_base_t *m, const char *key, void *value, int vsize) { + int n, err; + map_node_t **next, *node; + /* Find & replace existing node */ + next = map_getref(m, key); + if (next) { + memcpy((*next)->value, value, vsize); + return 0; + } + /* Add new node */ + node = map_newnode(key, value, vsize); + if (node == NULL) goto fail; + if (m->nnodes >= m->nbuckets) { + n = (m->nbuckets > 0) ? (m->nbuckets << 1) : 1; + err = map_resize(m, n); + if (err) goto fail; + } + map_addnode(m, node); + m->nnodes++; + return 0; + fail: + if (node) free(node); + return -1; +} + + +void map_remove_(map_base_t *m, const char *key) { + map_node_t *node; + map_node_t **next = map_getref(m, key); + if (next) { + node = *next; + *next = (*next)->next; + free(node); + m->nnodes--; + } +} + + +map_iter_t map_iter_(void) { + map_iter_t iter; + iter.bucketidx = -1; + iter.node = NULL; + return iter; +} + + +const char *map_next_(map_base_t *m, map_iter_t *iter) { + if (iter->node) { + iter->node = iter->node->next; + if (iter->node == NULL) goto nextBucket; + } else { + nextBucket: + do { + if (++iter->bucketidx >= m->nbuckets) { + return NULL; + } + iter->node = m->buckets[iter->bucketidx]; + } while (iter->node == NULL); + } + return (char*) (iter->node + 1); +} \ No newline at end of file diff --git a/src/shared/map.h b/src/shared/map.h new file mode 100644 index 0000000..ac64f7c --- /dev/null +++ b/src/shared/map.h @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2014 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef MAP_H +#define MAP_H + +#include + +#define MAP_VERSION "0.1.0" + +struct map_node_t; +typedef struct map_node_t map_node_t; + +typedef struct { + map_node_t **buckets; + unsigned nbuckets, nnodes; +} map_base_t; + +typedef struct { + unsigned bucketidx; + map_node_t *node; +} map_iter_t; + + +#define map_t(T)\ + struct { map_base_t base; T *ref; T tmp; } + + +#define map_init(m)\ + memset(m, 0, sizeof(*(m))) + + +#define map_deinit(m)\ + map_deinit_(&(m)->base) + + +#define map_get(m, key)\ + ( (m)->ref = map_get_(&(m)->base, key) ) + + +#define map_set(m, key, value)\ + ( (m)->tmp = (value),\ + map_set_(&(m)->base, key, &(m)->tmp, sizeof((m)->tmp)) ) + + +#define map_remove(m, key)\ + map_remove_(&(m)->base, key) + + +#define map_iter(m)\ + map_iter_() + + +#define map_next(m, iter)\ + map_next_(&(m)->base, iter) + + +void map_deinit_(map_base_t *m); +void *map_get_(map_base_t *m, const char *key); +int map_set_(map_base_t *m, const char *key, void *value, int vsize); +void map_remove_(map_base_t *m, const char *key); +map_iter_t map_iter_(void); +const char *map_next_(map_base_t *m, map_iter_t *iter); + + +typedef map_t(void*) map_void_t; +typedef map_t(char*) map_str_t; +typedef map_t(int) map_int_t; +typedef map_t(unsigned int) map_uint_t; +typedef map_t(char) map_char_t; +typedef map_t(float) map_float_t; +typedef map_t(double) map_double_t; + +#endif \ No newline at end of file