Compare commits

..

10 Commits

30 changed files with 603 additions and 274 deletions

View File

@ -3,7 +3,7 @@
cmake_minimum_required(VERSION 3.10)
project(simworld VERSION 0.0.1 LANGUAGES C)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=c17 -Wpedantic -pedantic-errors -Wformat=2 -Wshadow -Wwrite-strings -Wstrict-prototypes -g")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=gnu17 -Wpedantic -pedantic-errors -Wformat=2 -Wshadow -Wwrite-strings -Wstrict-prototypes -g")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

View File

@ -10,3 +10,5 @@ add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} PRIVATE simworld)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/common/include)
install(TARGETS ${PROJECT_NAME})

View File

@ -4,9 +4,11 @@
#include <unistd.h>
#include <sys/socket.h>
#include <world.h>
#include <error.h>
#include <request.h>
#include <world.h>
#include "render/render.h"
#include "sock.h"
int main(void) {
@ -16,21 +18,33 @@ int main(void) {
err = sock_init(&sock);
if (err) goto error;
char ibuf[8192] = { 0 };
read(sock, ibuf, 8192);
char input_buffer[8192] = { 0 };
char output_buffer[8192] = { 0 };
struct world_t world = { 0 };
err = world_deserialise_str(&world, ibuf);
// Send request for world data to server
struct request_body_get_world_data_t request_body = { 420 };
struct request_t request = { REQUEST_GET_WORLD_DATA, &request_body };
err = request_serialise_buf(&request, output_buffer, 8192);
if (err != ERR_OK) goto error_socket;
printf("CLIENT: (%zu %zu) ->", world.height++, world.width ++);
printf(" (%zu %zu)\n", world.height, world.width);
write(sock, output_buffer, 8192);
char obuf[8192] = { 0 };
err = world_serialise_buf(&world, obuf, 8192);
// Get response from server
read(sock, input_buffer, 8192);
struct response_t response = { 0 };
err = response_deserialise_str(&response, input_buffer);
if (err != ERR_OK) goto error_socket;
write(sock, obuf, 8192);
// Render data
if (response.success != true) {
err = ERR_REQUEST_FAILED;
goto error_socket;
}
struct response_body_get_world_data_t *response_body = response.body;
render_world(&response_body->world);
close(sock);
return ERR_OK;

View File

@ -1,11 +1,16 @@
// render/render.c
#include <assert.h>
#include <stdio.h>
#include "render.h"
void render_world(struct world_t const *world) {
assert(world != NULL);
printf("TICK: %zu\n", world->tick);
for (size_t i = 0; i < MAX_ENTITIES; i++) {
struct entity_t const *const entity = &world->entities[i];
struct entity_registrant_t const *const registrant =

View File

@ -11,8 +11,10 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/common/inc
find_package(PkgConfig REQUIRED)
pkg_check_modules(LUA REQUIRED lua)
pkg_check_modules(LUA REQUIRED lua5.4)
pkg_check_modules(JANSSON REQUIRED jansson)
target_link_libraries(${PROJECT_NAME} ${LUA_LIBRARIES} ${JANSSON_LIBRARIES})
target_include_directories(${PROJECT_NAME} PRIVATE ${LUA_INCLUDE_DIRS} ${JANSSON_INCLUDE_DIRS})
install(TARGETS ${PROJECT_NAME} DESTINATION lib)

View File

@ -1,6 +1,10 @@
#ifndef ERROR_H
#define ERROR_H
// TODO: Most of these aren't even errors used in this library.
// I should trim this down to only the ones belonging to the library, and make
// new enums for the client and server. Perhaps I can leverage __ERR_COUNT to
// have the client/server-specific enums start where this one ends
enum error_t {
ERR_OK,
ERR_INPUT,
@ -11,6 +15,10 @@ enum error_t {
ERR_SOCKET,
ERR_JSON_SERIALISE,
ERR_JSON_DESERIALISE,
ERR_INVALID_REQUEST,
ERR_REQUEST_FAILED,
ERR_THREAD,
ERR_MUTEX,
__ERR_COUNT,
};

View File

@ -2,11 +2,10 @@
#define COMMON_REQUEST_H
#include <stdbool.h>
#include <stdlib.h>
#include "error.h"
#include "world.h"
// ### REQUEST TYPES ### //
#include "request/get_world_data.h"
enum request_type_t {
REQUEST_NONE,
@ -14,11 +13,6 @@ enum request_type_t {
};
struct request_body_get_world_data_t {
size_t world_id;
};
struct request_t {
enum request_type_t type;
void *body;
@ -32,13 +26,6 @@ enum error_t request_deserialise_str(struct request_t *, char const *);
enum error_t request_serialise_buf(struct request_t const *, char *, size_t);
// ### RESPONSE TYPES ### //
struct response_body_get_world_data_t {
struct world_t world;
};
struct response_t {
enum request_type_t type;
bool success;

View File

@ -0,0 +1,41 @@
#ifndef COMMON_REQUEST_GET_WORLD_DATA_H
#define COMMON_REQUEST_GET_WORLD_DATA_H
#include <stdlib.h>
#include "../world.h"
struct request_body_get_world_data_t {
size_t world_id;
};
enum error_t request_body_get_world_data_serialise(
struct request_body_get_world_data_t const *,
struct json_t **
);
enum error_t request_body_get_world_data_deserialise(
struct request_body_get_world_data_t *,
struct json_t *
);
struct response_body_get_world_data_t {
struct world_t world;
};
enum error_t response_body_get_world_data_serialise(
struct response_body_get_world_data_t const *,
struct json_t **
);
enum error_t response_body_get_world_data_deserialise(
struct response_body_get_world_data_t *,
struct json_t *
);
#endif

View File

@ -15,6 +15,8 @@ struct world_t {
struct entity_t entities[MAX_ENTITIES];
struct entity_registry_t registered_entities;
size_t tick;
size_t height;
size_t width;
};

View File

@ -12,6 +12,10 @@ char const *const ERROR_STRS[] = {
"SOCKET",
"JSON SERIALISATION",
"JSON DESERIALISATION",
"INVALID REQUEST",
"REQUEST FAILED",
"THREAD INITIALISATION",
"MUTEX ERROR",
};

View File

@ -5,34 +5,9 @@
#include "request.h"
// Request Format Strings
static char const *REQUEST_JSON_FMT = "{si, so}";
static char const *REQUEST_BODY_GET_WORLD_DATA_JSON_FMT = "{sI}";
// Response Format Strings
static char const *RESPONSE_JSON_FMT = "{sb, si, so}";
static char const *RESPONSE_BODY_GET_WORLD_DATA_JSON_FMT = "{so}";
static enum error_t request_serialise_body_get_world_data(
struct request_body_get_world_data_t const *self,
struct json_t **jsonptr
) {
assert(self != NULL);
assert(jsonptr != NULL);
struct json_t *json = json_pack(REQUEST_BODY_GET_WORLD_DATA_JSON_FMT,
"world-id", self->world_id
);
if (json == NULL) return ERR_JSON_SERIALISE;
*jsonptr = json;
return ERR_OK;
}
static enum error_t request_serialise_body(
struct request_t const *self,
@ -43,7 +18,7 @@ static enum error_t request_serialise_body(
switch (self->type) {
case REQUEST_GET_WORLD_DATA:
return request_serialise_body_get_world_data(self->body, jsonptr);
return request_body_get_world_data_serialise(self->body, jsonptr);
default: return ERR_JSON_SERIALISE;
}
@ -162,22 +137,6 @@ static enum error_t request_deserialise_parts(
}
static enum error_t request_deserialise_body_get_world_data(
struct request_body_get_world_data_t *self,
struct json_t *json
) {
assert(self != NULL);
assert(json != NULL);
int err = json_unpack(json, REQUEST_BODY_GET_WORLD_DATA_JSON_FMT,
"world-id", &self->world_id
);
if (err < 0) return ERR_JSON_DESERIALISE;
return ERR_OK;
}
static enum error_t request_deserialise_body(
struct request_t *self,
struct json_t *json
@ -190,7 +149,7 @@ static enum error_t request_deserialise_body(
self->body = malloc(sizeof(struct request_body_get_world_data_t));
if (self->body == NULL) return ERR_ALLOC;
return request_deserialise_body_get_world_data(self->body, json);
return request_body_get_world_data_deserialise(self->body, json);
default: return ERR_JSON_SERIALISE;
}
@ -241,22 +200,6 @@ enum error_t request_deserialise_str(struct request_t *self, char const *str) {
}
static enum error_t response_serialise_body_get_world_data(
struct response_body_get_world_data_t const *self,
struct json_t **jsonptr
) {
assert(self != NULL);
assert(jsonptr != NULL);
struct json_t *json = NULL;
enum error_t err = world_serialise(&self->world, &json);
if (err != ERR_OK) return err;
*jsonptr = json;
return ERR_OK;
}
static enum error_t response_serialise_body(
struct response_t const *self,
struct json_t **jsonptr
@ -266,7 +209,7 @@ static enum error_t response_serialise_body(
switch (self->type) {
case REQUEST_GET_WORLD_DATA:
return response_serialise_body_get_world_data(self->body, jsonptr);
return response_body_get_world_data_serialise(self->body, jsonptr);
default: return ERR_JSON_SERIALISE;
}
@ -393,20 +336,6 @@ static enum error_t response_deserialise_parts(
}
static enum error_t response_deserialise_body_get_world_data(
struct response_body_get_world_data_t *self,
struct json_t *json
) {
assert(self != NULL);
assert(json != NULL);
enum error_t err = world_deserialise(&self->world, json);
if (err != ERR_OK) return err;
return ERR_OK;
}
static enum error_t response_deserialise_body(
struct response_t *self,
struct json_t *json
@ -419,7 +348,7 @@ static enum error_t response_deserialise_body(
self->body = malloc(sizeof(struct response_body_get_world_data_t));
if (self->body == NULL) return ERR_ALLOC;
return response_deserialise_body_get_world_data(self->body, json);
return response_body_get_world_data_deserialise(self->body, json);
default: return ERR_JSON_SERIALISE;
}

View File

@ -0,0 +1,71 @@
// request/get_world_data.c
#include <assert.h>
#include "request/get_world_data.h"
static char const *REQUEST_BODY_GET_WORLD_DATA_JSON_FMT = "{sI}";
enum error_t request_body_get_world_data_serialise(
struct request_body_get_world_data_t const *self,
struct json_t **jsonptr
) {
assert(self != NULL);
assert(jsonptr != NULL);
struct json_t *json = json_pack(REQUEST_BODY_GET_WORLD_DATA_JSON_FMT,
"world-id", self->world_id
);
if (json == NULL) return ERR_JSON_SERIALISE;
*jsonptr = json;
return ERR_OK;
}
enum error_t request_body_get_world_data_deserialise(
struct request_body_get_world_data_t *self,
struct json_t *json
) {
assert(self != NULL);
assert(json != NULL);
int err = json_unpack(json, REQUEST_BODY_GET_WORLD_DATA_JSON_FMT,
"world-id", &self->world_id
);
if (err < 0) return ERR_JSON_DESERIALISE;
return ERR_OK;
}
enum error_t response_body_get_world_data_serialise(
struct response_body_get_world_data_t const *self,
struct json_t **jsonptr
) {
assert(self != NULL);
assert(jsonptr != NULL);
struct json_t *json = NULL;
enum error_t err = world_serialise(&self->world, &json);
if (err != ERR_OK) return err;
*jsonptr = json;
return ERR_OK;
}
enum error_t response_body_get_world_data_deserialise(
struct response_body_get_world_data_t *self,
struct json_t *json
) {
assert(self != NULL);
assert(json != NULL);
enum error_t err = world_deserialise(&self->world, json);
if (err != ERR_OK) return err;
return ERR_OK;
}

View File

@ -5,12 +5,14 @@
#include "world.h"
static char const *const WORLD_JSON_FMT = "{so, so, sI, sI}";
static char const *const WORLD_JSON_FMT = "{so, so, sI, sI, sI}";
enum error_t world_init(struct world_t *self, size_t height, size_t width) {
assert(self != NULL);
self->tick = 0;
self->height = height;
self->width = width;
@ -84,7 +86,7 @@ static enum error_t world_serialise_entities(
static enum error_t world_serialise_parts(
struct json_t *entities_json,
struct json_t *registered_entities_json,
size_t height, size_t width,
size_t tick, size_t height, size_t width,
struct json_t **json
) {
assert(entities_json != NULL);
@ -94,7 +96,7 @@ static enum error_t world_serialise_parts(
struct json_t *world_json = json_pack(WORLD_JSON_FMT,
"entities", entities_json,
"registered-entities", registered_entities_json,
"height", height, "width", width
"tick", tick, "height", height, "width", width
);
if (world_json == NULL) return ERR_JSON_SERIALISE;
@ -131,7 +133,7 @@ enum error_t world_serialise(
err = world_serialise_parts(
entities_json,
registered_entities_json,
self->height, self->width,
self->tick, self->height, self->width,
&world_json
);
if (err != ERR_OK) goto error_world;
@ -150,7 +152,7 @@ error:
static enum error_t world_deserialise_parts(
struct json_t **entities_json,
struct json_t **registered_entities_json,
size_t *height, size_t *width,
size_t *tick, size_t *height, size_t *width,
struct json_t *json
) {
assert(entities_json != NULL);
@ -162,7 +164,7 @@ static enum error_t world_deserialise_parts(
int err = json_unpack(json, WORLD_JSON_FMT,
"entities", entities_json,
"registered-entities", registered_entities_json,
"height", height, "width", width
"tick", tick, "height", height, "width", width
);
if (err < 0) return ERR_JSON_DESERIALISE;
@ -230,7 +232,7 @@ enum error_t world_deserialise(struct world_t *self, struct json_t *json) {
enum error_t err = world_deserialise_parts(
&entities_json,
&registered_entities_json,
&temp_world.height, &temp_world.width,
&temp_world.tick, &temp_world.height, &temp_world.width,
json
);
if (err != ERR_OK) return err;

View File

@ -2,12 +2,18 @@
A list of things I'd like to accomplish
## In Progress
* Add a lua modding API
* Load mods
* Handle mod dependencies
## Completed
* Specify mods directory
* Tick-based game loop
* Split game ticking and socket connecting logic (can only tick on connection atm)
* Client send request
* Server handle request and send response
* Client handle response
* Have client render world data (return to pre-daemonised equivalent state)
## Completed
* Serialise data to JSON for socket data transmission
* Send world data to client
* Initialise a world again
@ -19,14 +25,13 @@ A list of things I'd like to accomplish
* Write Makefile to automate compilation
## Planned
* Split game ticking and socket connecting logic (can only tick on connection atm)
* Requests/responses for more than just the complete set of world data all at once
* Create and load worlds
* Display environment and pan camera
* Time controls (play/pause/speed up)
* See creature stats
* Add a lua modding API
* Load mods
* Handle mod dependencies
* Remove rendering from serverside
* Client-side resource files for each mod
* Real error handling (right now I just pass up to main and immediately exit)
* Portability to other UNIX-like operating systems (if changes are needed; I don't care about DOS-likes)
* Reduce repetitiveness of JSON (de)serialisation code

3
mods/core/init.lua Normal file
View File

@ -0,0 +1,3 @@
-- init.lua
print("Hello, world!")

6
mods/core/mod.json Normal file
View File

@ -0,0 +1,6 @@
{
"name": "core",
"version": "0.0.1",
"depends": [],
"optdepends": []
}

View File

@ -7,16 +7,40 @@ if ! [ -d ".git" ]; then
exit 1
fi
OPTIND=1
server_args=""
client1_args=""
client2_args=""
client3_args=""
while getopts "d:1:2:3:" opt; do
case "${opt}" in
d) server_args="${OPTARG}" ;;
1) client1_args="${OPTARG}" ;;
2) client2_args="${OPTARG}" ;;
3) client3_args="${OPTARG}" ;;
*) echo "Invalid flag"; exit 1
esac
done
shift $((OPTIND - 1))
[ "${1:-}" = "--" ] && shift
./scripts/build.sh
echo "### DAEMON ###"
./build/server/simworld-daemon &
./build/server/simworld-daemon ${server_args} &
sleep 1
echo "### CLIENT 1 ###"
./build/client/simworld-client
./build/client/simworld-client ${client1_args}
echo "### CLIENT 2 ###"
./build/client/simworld-client
./build/client/simworld-client ${client2_args}
echo "### CLIENT 3 ###"
sleep 1
./build/client/simworld-client ${client3_args}
killall simworld-daemon

View File

@ -9,3 +9,5 @@ file(GLOB_RECURSE SOURCES ${SOURCE_DIR}/*.c)
add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} PRIVATE simworld)
install(TARGETS ${PROJECT_NAME})

View File

@ -1,16 +0,0 @@
#ifndef DAEMON_DATA_H
#define DAEMON_DATA_H
#include <world.h>
#include "opts.h"
struct data_t {
struct options_t options;
struct world_t world;
int socket;
int socket_accept;
};
#endif

67
server/src/game.c Normal file
View File

@ -0,0 +1,67 @@
// game.c
#include <assert.h>
#include <stdarg.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include "game.h"
void write_log(bool is_daemon, char const *fmt, ...) {
va_list args;
va_start(args, fmt);
if (is_daemon) vsyslog(LOG_INFO, fmt, args);
else vprintf(fmt, args);
va_end(args);
}
enum error_t game_load_mods(struct game_t *game) {
assert(game != NULL);
if (game->options.mods_directory == NULL) return ERR_NOTFOUND;
DIR *mods_directory = opendir(game->options.mods_directory);
if (mods_directory == NULL) return ERR_NOTFOUND;
// Imagine handling errors properly
struct dirent *ent = NULL;
while ((ent = readdir(mods_directory)) != NULL) {
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
size_t abs_path_len = strlen(
game->options.mods_directory) + strlen(ent->d_name
);
char* abs_path = calloc(abs_path_len, sizeof(char));
strcat(abs_path, game->options.mods_directory);
strcat(abs_path, "/");
strcat(abs_path, ent->d_name);
struct stat file_info = { 0 };
if (stat(abs_path, &file_info) != 0) {
free(abs_path);
return ERR_NOTFOUND;
}
free(abs_path);
if (S_ISREG(file_info.st_mode))
game_log(game, "FOUND REGULAR FILE: ");
else if (S_ISDIR(file_info.st_mode))
game_log(game, "FOUND DIRECTORY: ");
game_log(game, "%s\n", ent->d_name);
}
closedir(mods_directory);
return ERR_OK;
}

32
server/src/game.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef GAME_H
#define GAME_H
#include <pthread.h>
#include <world.h>
#include "opts.h"
struct game_t {
struct options_t options;
struct world_t world;
pthread_mutex_t world_lock;
};
struct mod_t {
char const *path;
struct mod_t **depends;
struct mod_t **opt_depends;
};
void write_log(bool, char const *, ...);
#define game_log(game, fmt, ...) \
write_log((game)->options.daemonise, fmt, ##__VA_ARGS__)
enum error_t game_load_mods(struct game_t *);
#endif

View File

@ -1,5 +1,6 @@
// main.c
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
@ -12,61 +13,48 @@
#include <request.h>
#include "opts.h"
#include "sock.h"
#include "data.h"
#include "socket.h"
#include "game.h"
static void handle_signal(int signal_no) {
(void)signal_no;
remove(SOCK_PATH);
remove(SOCKET_PATH);
exit(0);
}
enum error_t game_loop(struct data_t *data) {
enum error_t err = ERR_OK;
static enum error_t simulation_thread_body(struct game_t *game) {
assert(game != NULL);
// TEMP
struct response_body_get_world_data_t response_body = { data->world };
struct response_t response = { true, REQUEST_GET_WORLD_DATA, &response_body };
// TODO: An actual game loop lol
while (true) {
if (pthread_mutex_lock(&game->world_lock) != 0) goto error;
game->world.tick += 1;
if (pthread_mutex_unlock(&game->world_lock) != 0) goto error;
char const *response_json_str = NULL;
err = response_serialise_str(&response, &response_json_str);
if (err) return err;
sleep(1);
}
printf("serialised response: %s\n", response_json_str);
return ERR_OK;
struct response_t response2 = { 0 };
err = response_deserialise_str(&response2, response_json_str);
if (err) return err;
error:
return ERR_MUTEX;
}
struct response_body_get_world_data_t *response2_body = response2.body;
char const *world_json_str = NULL;
err = world_serialise_str(&response2_body->world, &world_json_str);
if (err) return err;
printf("deserialised response: (%d, %d, %s\n",
response2.success,
response2.type,
world_json_str
static enum error_t simulation_thread(
pthread_t *pthread,
struct game_t *data
) {
assert(pthread != NULL);
assert(data != NULL);
int err = pthread_create(
pthread, NULL,
(void *(*)(void *))simulation_thread_body, data
);
free((void *)response_json_str);
// TEMP
char obuf[8192] = { 0 };
err = world_serialise_buf(&data->world, obuf, 8192);
if (err != ERR_OK) return err;
write(data->socket_accept, obuf, 8192);
char ibuf[8192] = { 0 };
read(data->socket_accept, ibuf, 8192);
err = world_deserialise_str(&data->world, ibuf);
if (err != ERR_OK) return err;
printf("SERVER: \"%s\" -> \"%s\"\n", obuf, ibuf);
if (err != 0) return ERR_THREAD;
return ERR_OK;
}
@ -75,7 +63,8 @@ enum error_t game_loop(struct data_t *data) {
int main(int argc, char **argv) {
// Set up variables
enum error_t err = ERR_OK;
struct data_t data = { 0 };
struct game_t data = { 0 };
// Signal handling; TODO: should probably improve this
signal(SIGINT, handle_signal);
@ -94,6 +83,13 @@ int main(int argc, char **argv) {
err = world_init(&data.world, 10, 10);
if (err) goto handle_error;
// Load mods (yes, after world_init() though that sounds really wrong
// and most definitely should be changed)
err = game_load_mods(&data);
if (err) goto handle_error;
pthread_mutex_init(&data.world_lock, NULL);
err = world_register_entity(&data.world, "john", 'j');
if (err) goto handle_error_world;
@ -106,16 +102,34 @@ int main(int argc, char **argv) {
entity_init(&data.world.entities[26], 2, 2, 6);
// Make a gap in the entity array to see if "ghost" entities remain
// Socket handling and run gameloop
err = sock_loop(&data, game_loop);
if (err) goto handle_error;
// Socket handler thread
pthread_t pthread_socket;
err = socket_thread(&pthread_socket, &data);
if (err) goto handle_error_world;
// Simulation thread
pthread_t pthread_simulation;
err = simulation_thread(&pthread_simulation, &data);
if (err) goto handle_error_world;
// Join threads
// TODO: A way to shut down the program properly
pthread_join(pthread_socket, (void **)&err); // No way this is correct lol
if (err) goto handle_error_world;
pthread_join(pthread_simulation, (void **)&err);
if (err) goto handle_error_world;
// Deinitialisation
pthread_mutex_destroy(&data.world_lock);
world_free(&data.world);
opts_free(&data.options);
return ERR_OK;
handle_error_world:
pthread_mutex_destroy(&data.world_lock);
world_free(&data.world);
handle_error:
@ -123,6 +137,6 @@ handle_error:
opts_free(&data.options);
handle_error_pre_opts:
printf("SERVER ERROR: %s\n", ERROR_STRS[err]);
write_log(data.options.daemonise, "SERVER ERROR: %s\n", ERROR_STRS[err]);
return err;
}

View File

@ -2,6 +2,7 @@
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/syslog.h>
#include <getopt.h>
@ -11,22 +12,29 @@
void opts_default(struct options_t *options) {
options->daemonise = false;
// TODO: Decide on a real default mods directory
options->mods_directory = calloc(4096, sizeof(char));
getcwd(options->mods_directory, 4096);
}
enum error_t opts_parse(struct options_t *options, int argc, char **argv) {
struct option const long_options[] = {
{ "daemon", no_argument, 0, 'd' },
{ "mods-directory", required_argument, 0, 'm' },
{ NULL, 0, 0, 0 }
};
while (true) {
int option_index = 0;
int c = getopt_long(argc, argv, "d", long_options, &option_index);
int c = getopt_long(argc, argv, "dm:", long_options, &option_index);
if (c == -1) break;
switch (c) {
case 'd': options->daemonise = true; break;
case 'm': options->mods_directory = optarg; break;
default: return ERR_INPUT;
}
}

View File

@ -6,6 +6,7 @@
#include <error.h>
struct options_t {
char* mods_directory;
bool daemonise;
};

View File

@ -1,3 +1,63 @@
// request.c
#include <assert.h>
#include "request.h"
static struct response_t response_failure(
enum request_type_t request
) {
return (struct response_t){ request, false, NULL };
}
static void handle_request_get_world_data(
struct response_t *response,
struct request_t const *request,
struct game_t *ctx
) {
assert(response != NULL);
assert(request != NULL);
assert(ctx != NULL);
// TODO: World creation isn't set up yet, so the world-id parameter isn't
// useful right now, but make sure to use it later when it is set up
struct response_body_get_world_data_t *body =
malloc(sizeof(struct response_body_get_world_data_t));
if (body == NULL) goto error;
response->body = body;
if (pthread_mutex_lock(&ctx->world_lock) != 0) goto error;
body->world = ctx->world;
if (pthread_mutex_unlock(&ctx->world_lock) != 0) goto error;
response->type = request->type;
response->success = true;
return;
error:
free(body);
*response = response_failure(request->type);
}
struct response_t handle_request(
struct request_t const *request,
struct game_t *ctx
) {
assert(request != NULL);
assert(ctx != NULL);
struct response_t response = { 0 };
switch (request->type) {
case REQUEST_GET_WORLD_DATA:
handle_request_get_world_data(&response, request, ctx);
return response;
default:
return response_failure(request->type);
}
}

View File

@ -1,8 +1,13 @@
#ifndef REQUEST_H
#define REQUEST_H
#include <error.h>
#include <request.h>
enum error_t handle_request(void);
#include "game.h"
struct response_t handle_request(
struct request_t const *,
struct game_t *
);
#endif

View File

@ -1,74 +0,0 @@
// sock.c
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "sock.h"
enum error_t sock_init(int *sockptr) {
assert(sockptr != NULL);
int const sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) goto sock_init_error;
struct sockaddr_un sa = { 0 };
sa.sun_family = AF_UNIX;
strcpy(sa.sun_path, SOCK_PATH);
remove(SOCK_PATH);
// Should be redundant but it doesn't hurt to be sure
if (bind(sock, (struct sockaddr *)&sa, sizeof(sa))) goto sock_error;
if (listen(sock, 4096) < 0) goto sock_error;
*sockptr = sock;
return ERR_OK;
sock_error:
sock_free(&sock);
sock_init_error:
return ERR_SOCKET;
}
void sock_free(int const *sockptr) {
assert(sockptr != NULL);
close(*sockptr);
remove(SOCK_PATH);
}
enum error_t sock_loop(struct data_t *data, gameloop_fn fn) {
assert(data != NULL);
enum error_t err = ERR_OK;
if ((err = sock_init(&data->socket))) return err;
for (int sock_accept;;) {
if ((sock_accept = accept(data->socket, NULL, NULL)) < 0) {
err = ERR_SOCKET;
goto sock_loop_end_pre;
};
data->socket_accept = sock_accept;
if ((err = fn(data))) goto sock_loop_end_pre;
close(data->socket_accept);
continue;
sock_loop_end_pre:
close(data->socket_accept);
goto sock_loop_end;
}
sock_loop_end:
sock_free(&data->socket);
return err;
}

View File

@ -1,17 +0,0 @@
#ifndef SOCK_H
#define SOCK_H
#include <error.h>
#include "data.h"
#define SOCK_PATH "/tmp/swd.sock"
// Socket path should be a shared setting between server and client
enum error_t sock_init(int *);
void sock_free(int const *);
typedef enum error_t (* gameloop_fn)(struct data_t *);
enum error_t sock_loop(struct data_t *, gameloop_fn);
#endif

125
server/src/socket.c Normal file
View File

@ -0,0 +1,125 @@
// socket.c
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "socket.h"
#include "request.h"
enum error_t socket_init(int *sockptr) {
assert(sockptr != NULL);
int const sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) goto error;
struct sockaddr_un sa = { 0 };
sa.sun_family = AF_UNIX;
strcpy(sa.sun_path, SOCKET_PATH);
remove(SOCKET_PATH); // Should be redundant but it doesn't hurt to be sure
if (bind(sock, (struct sockaddr *)&sa, sizeof(sa))) goto error_free_sock;
if (listen(sock, 4096) < 0) goto error_free_sock;
*sockptr = sock;
return ERR_OK;
error_free_sock:
socket_free(&sock);
error:
return ERR_SOCKET;
}
void socket_free(int const *sockptr) {
assert(sockptr != NULL);
close(*sockptr);
remove(SOCKET_PATH);
}
// TODO: Out of curiosity, what's the consensus on struct declarations outside
// of header files? Its a private struct that the end-user should never need to
// use, so I don't want to include it in socket.h, but it feels off putting it
// in socket.c as well just because I'm used to them all being in .h
struct socket_data_t {
struct game_t *game_data;
int socket;
};
static enum error_t socket_handle(struct socket_data_t *data) {
enum error_t err = ERR_OK;
char output_buffer[8192] = { 0 };
char input_buffer[8192] = { 0 };
// Receive request from client
read(data->socket, input_buffer, 8192);
struct request_t request = { 0 };
err = request_deserialise_str(&request, input_buffer);
if (err != ERR_OK) return err;
// Send response to client
struct response_t response = handle_request(&request, data->game_data);
err = response_serialise_buf(&response, output_buffer, 8192);
if (err != ERR_OK) return err;
write(data->socket, output_buffer, 8192);
return ERR_OK;
}
static enum error_t socket_thread_body(struct game_t *data) {
assert(data != NULL);
enum error_t err = ERR_OK;
struct socket_data_t socket_data = { data, 0 };
int socket = 0;
if ((err = socket_init(&socket))) return err;
for (int sock_accept;;) {
if ((sock_accept = accept(socket, NULL, NULL)) < 0) {
err = ERR_SOCKET;
goto error_pre;
};
socket_data.socket = sock_accept;
if ((err = socket_handle(&socket_data))) goto error_pre;
close(socket_data.socket);
continue;
error_pre:
close(socket_data.socket);
goto error;
}
error:
socket_free(&socket);
return err;
}
enum error_t socket_thread(pthread_t *pthread, struct game_t *data) {
assert(pthread != NULL);
assert(data != NULL);
int err = pthread_create(
pthread, NULL,
(void *(*)(void *))socket_thread_body, // I want to cry tf is this
data
);
if (err != 0) return ERR_THREAD;
return ERR_OK;
}

17
server/src/socket.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef SOCKET_H
#define SOCKET_H
#include <error.h>
#include "game.h"
#define SOCKET_PATH "/tmp/swd.sock"
// Socket path should be a shared setting between server and client
enum error_t socket_init(int *);
void socket_free(int const *);
enum error_t socket_thread(pthread_t *, struct game_t *);
#endif