More progress. Host can now see the client that has joined. Host reacts to networked messages.

This commit is contained in:
2025-12-16 10:51:06 +01:00
parent 996b1a3404
commit 4ea71838b9
8 changed files with 366 additions and 19 deletions

View File

@@ -5,7 +5,7 @@
#define INPUT_QUEUE_CAPACITY 8 #define INPUT_QUEUE_CAPACITY 8
void game_instance_init(Game_Instance *instance) { void game_instance_init(Game_Instance *instance) {
instance->local_player_index = 0xFFFF; instance->local_player_id = 0xFFFF;
// TODO: SS - Init input-queue. // TODO: SS - Init input-queue.
instance->simulation_accumulator = 0.0f; // TODO: SS - Should probably be moved to the Ingame context. instance->simulation_accumulator = 0.0f; // TODO: SS - Should probably be moved to the Ingame context.
instance->game_session = NULL; instance->game_session = NULL;
@@ -38,18 +38,24 @@ bool game_instance_host_session(Game_Instance *instance, const Game_Session_Sett
return false; 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"); printf("Failed to host session.\n");
free(instance->game_session); free(instance->game_session);
instance->game_session = NULL; instance->game_session = NULL;
return false; return false;
} }
instance->local_player_id = host_player_id;
} }
else { else {
game_session_init(instance->game_session, settings); game_session_init(instance->game_session, settings);
}
assert(game_session_create_player(instance->game_session, &instance->local_player_index)); // Create the host (you!). 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.
}
}
return true; return true;
} }

View File

@@ -9,7 +9,7 @@
#include "shared/squeue.h" #include "shared/squeue.h"
typedef struct { typedef struct {
uint16_t local_player_index; uint16_t local_player_id;
SQueue local_input_queue; SQueue local_input_queue;
double simulation_accumulator; double simulation_accumulator;

View File

@@ -77,7 +77,7 @@ static void state_tick(Presentation_State *state) {
if(game_instance_pop_local_input(instance, &input)) { 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.) // 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. // 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); game_session_tick(session);
@@ -299,7 +299,7 @@ static void state_render(Presentation_State *state) {
sim_world->game_world->seed, sim_world->game_world->seed,
sim_world->game_world->grid.width, sim_world->game_world->grid.height, sim_world->game_world->grid.width, sim_world->game_world->grid.height,
session->settings.tickrate, 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 game_session_get_amount_of_active_players(session), sim_world->max_players
); );

View File

@@ -6,15 +6,62 @@
static void listen_callback( static void listen_callback(
const char *event, /* "joined", "leaved", "game" */ const char *event, /* "joined", "leaved", "game" */
int64_t messageId, /* sekventiellt meddelandeID (från host) */ int64_t messsage_id, /* sekventiellt meddelandeID (från host) */
const char *clientId, /* avsändarens klientID (eller NULL) */ const char *client_id, /* avsändarens klientID (eller NULL) */
json_t *data, /* JSONobjekt med godtycklig speldata */ json_t *data, /* JSONobjekt med godtycklig speldata */
void *context /* godtycklig pekare som skickas vidare */ void *context /* godtycklig pekare som skickas vidare */
) { ) {
printf("#%li :: Event: '%s' from '%s'.\n", messageId, event, clientId);
Game_Networking *networking = (Game_Networking *)context; 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) { 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) { static bool stop_listening(Game_Networking *networking) {
assert(networking != NULL); assert(networking != NULL);
assert(networking->api != NULL); assert(networking->api != NULL);
assert(networking->listener_id >= 0);
mp_api_unlisten(networking->api, (int)networking->listener_id); 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(networking != NULL);
assert(session != NULL);
if(!settings.online) { if(!settings.online) {
printf("Failed to host; Game_Session_Settings.online == false, expected it to be true.\n"); 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); 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->session_id = session_id;
networking->local_client_id = local_client_id; networking->local_client_id = local_client_id;
map_init(&networking->client_id_to_player_index);
assert(start_listening(networking)); assert(start_listening(networking));
// Set up session. // Set up session.
@@ -98,6 +149,13 @@ bool networking_try_host(Game_Networking *networking, Game_Session *session, Gam
settings 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) { if(response_data != NULL) {
// TODO: SS - Handle JSON-response. // TODO: SS - Handle JSON-response.
json_decref(response_data); json_decref(response_data);
@@ -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) { bool networking_try_join(Game_Networking *networking, Game_Session *session, const char *session_id) {
assert(networking != NULL); assert(networking != NULL);
assert(session != NULL);
if(strlen(session_id) == 0) { if(strlen(session_id) == 0) {
return false; return false;
@@ -127,7 +186,7 @@ bool networking_try_join(Game_Networking *networking, Game_Session *session, con
networking->api, // API. networking->api, // API.
session_id, // Session-code. session_id, // Session-code.
NULL, // JSON-data to send. // TODO: SS - Send information about you as a player (username, ..). 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. &local_client_id, // Received client-id.
&received_json // Received JSON-data. &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); printf("Failed to join; Got result: %i.\n", join_result);
return false; 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); 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->session_id = result_session_id;
networking->local_client_id = local_client_id; networking->local_client_id = local_client_id;
map_init(&networking->client_id_to_player_index);
// TODO: SS - Decide how to network this.. // 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? // Or, should this client receive the current state of the session?
if(received_json != NULL) { if(received_json != NULL) {
@@ -170,10 +232,13 @@ bool networking_stop_hosting(Game_Networking *networking) {
return false; return false;
} }
map_deinit(&networking->client_id_to_player_index);
assert(stop_listening(networking)); assert(stop_listening(networking));
mp_api_destroy(networking->api); mp_api_destroy(networking->api);
networking->api = NULL; networking->api = NULL;
networking->session = NULL;
if(networking->session_id != NULL) { if(networking->session_id != NULL) {
free(networking->session_id); free(networking->session_id);

View File

@@ -4,6 +4,8 @@
#include "multiplayer_api.h" #include "multiplayer_api.h"
#include "game_session.h" #include "game_session.h"
#include "shared/map.h"
typedef struct { typedef struct {
MultiplayerApi *api; MultiplayerApi *api;
@@ -11,9 +13,12 @@ typedef struct {
char *local_client_id; char *local_client_id;
uint32_t listener_id; uint32_t listener_id;
Game_Session *game_session; // Comes from Game_Instance.
map_uint_t client_id_to_player_index;
} Game_Networking; } 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_try_join(Game_Networking *networking, Game_Session *session, const char *session_id);
bool networking_stop_hosting(Game_Networking *networking); bool networking_stop_hosting(Game_Networking *networking);

193
src/shared/map.c Normal file
View File

@@ -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 <stdlib.h>
#include <string.h>
#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);
}

78
src/shared/map.h Normal file
View File

@@ -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 <string.h>
#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