More progress. Host can now see the client that has joined. Host reacts to networked messages.
This commit is contained in:
@@ -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,18 +38,24 @@ 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -98,6 +149,13 @@ bool networking_try_host(Game_Networking *networking, Game_Session *session, Gam
|
||||
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.
|
||||
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) {
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
193
src/shared/map.c
Normal file
193
src/shared/map.c
Normal 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
78
src/shared/map.h
Normal 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
|
||||
Reference in New Issue
Block a user