Compare commits
No commits in common. "b3261454d954920a1499cb9dcddb370fe056c244" and "c32af03d209dc5b9956c753d6ab9a8e96fa21d6a" have entirely different histories.
b3261454d9
...
c32af03d20
@ -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=gnu17 -Wpedantic -pedantic-errors -Wformat=2 -Wshadow -Wwrite-strings -Wstrict-prototypes -g")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=c17 -Wpedantic -pedantic-errors -Wformat=2 -Wshadow -Wwrite-strings -Wstrict-prototypes -g")
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
|
@ -10,5 +10,3 @@ 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})
|
||||
|
@ -4,11 +4,9 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <error.h>
|
||||
#include <request.h>
|
||||
#include <world.h>
|
||||
#include <error.h>
|
||||
|
||||
#include "render/render.h"
|
||||
#include "sock.h"
|
||||
|
||||
int main(void) {
|
||||
@ -18,33 +16,21 @@ int main(void) {
|
||||
err = sock_init(&sock);
|
||||
if (err) goto error;
|
||||
|
||||
char input_buffer[8192] = { 0 };
|
||||
char output_buffer[8192] = { 0 };
|
||||
char ibuf[8192] = { 0 };
|
||||
read(sock, ibuf, 8192);
|
||||
|
||||
// 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);
|
||||
struct world_t world = { 0 };
|
||||
err = world_deserialise_str(&world, ibuf);
|
||||
if (err != ERR_OK) goto error_socket;
|
||||
|
||||
write(sock, output_buffer, 8192);
|
||||
printf("CLIENT: (%zu %zu) ->", world.height++, world.width ++);
|
||||
printf(" (%zu %zu)\n", world.height, world.width);
|
||||
|
||||
// Get response from server
|
||||
read(sock, input_buffer, 8192);
|
||||
|
||||
struct response_t response = { 0 };
|
||||
err = response_deserialise_str(&response, input_buffer);
|
||||
char obuf[8192] = { 0 };
|
||||
err = world_serialise_buf(&world, obuf, 8192);
|
||||
if (err != ERR_OK) goto error_socket;
|
||||
|
||||
// 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);
|
||||
write(sock, obuf, 8192);
|
||||
|
||||
close(sock);
|
||||
return ERR_OK;
|
||||
|
@ -1,16 +1,11 @@
|
||||
// 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 =
|
||||
|
@ -11,10 +11,8 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/common/inc
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
pkg_check_modules(LUA REQUIRED lua5.4)
|
||||
pkg_check_modules(LUA REQUIRED lua)
|
||||
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)
|
||||
|
@ -1,10 +1,6 @@
|
||||
#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,
|
||||
@ -15,10 +11,6 @@ enum error_t {
|
||||
ERR_SOCKET,
|
||||
ERR_JSON_SERIALISE,
|
||||
ERR_JSON_DESERIALISE,
|
||||
ERR_INVALID_REQUEST,
|
||||
ERR_REQUEST_FAILED,
|
||||
ERR_THREAD,
|
||||
ERR_MUTEX,
|
||||
__ERR_COUNT,
|
||||
};
|
||||
|
||||
|
@ -2,10 +2,11 @@
|
||||
#define COMMON_REQUEST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "request/get_world_data.h"
|
||||
#include "world.h"
|
||||
|
||||
// ### REQUEST TYPES ### //
|
||||
|
||||
enum request_type_t {
|
||||
REQUEST_NONE,
|
||||
@ -13,6 +14,11 @@ 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;
|
||||
@ -26,6 +32,13 @@ 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;
|
||||
|
@ -1,41 +0,0 @@
|
||||
#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
|
@ -15,8 +15,6 @@ struct world_t {
|
||||
struct entity_t entities[MAX_ENTITIES];
|
||||
struct entity_registry_t registered_entities;
|
||||
|
||||
size_t tick;
|
||||
|
||||
size_t height;
|
||||
size_t width;
|
||||
};
|
||||
|
@ -12,10 +12,6 @@ char const *const ERROR_STRS[] = {
|
||||
"SOCKET",
|
||||
"JSON SERIALISATION",
|
||||
"JSON DESERIALISATION",
|
||||
"INVALID REQUEST",
|
||||
"REQUEST FAILED",
|
||||
"THREAD INITIALISATION",
|
||||
"MUTEX ERROR",
|
||||
};
|
||||
|
||||
|
||||
|
@ -5,9 +5,34 @@
|
||||
|
||||
#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,
|
||||
@ -18,7 +43,7 @@ static enum error_t request_serialise_body(
|
||||
|
||||
switch (self->type) {
|
||||
case REQUEST_GET_WORLD_DATA:
|
||||
return request_body_get_world_data_serialise(self->body, jsonptr);
|
||||
return request_serialise_body_get_world_data(self->body, jsonptr);
|
||||
|
||||
default: return ERR_JSON_SERIALISE;
|
||||
}
|
||||
@ -137,6 +162,22 @@ 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
|
||||
@ -149,7 +190,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_body_get_world_data_deserialise(self->body, json);
|
||||
return request_deserialise_body_get_world_data(self->body, json);
|
||||
|
||||
default: return ERR_JSON_SERIALISE;
|
||||
}
|
||||
@ -200,6 +241,22 @@ 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
|
||||
@ -209,7 +266,7 @@ static enum error_t response_serialise_body(
|
||||
|
||||
switch (self->type) {
|
||||
case REQUEST_GET_WORLD_DATA:
|
||||
return response_body_get_world_data_serialise(self->body, jsonptr);
|
||||
return response_serialise_body_get_world_data(self->body, jsonptr);
|
||||
|
||||
default: return ERR_JSON_SERIALISE;
|
||||
}
|
||||
@ -336,6 +393,20 @@ 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
|
||||
@ -348,7 +419,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_body_get_world_data_deserialise(self->body, json);
|
||||
return response_deserialise_body_get_world_data(self->body, json);
|
||||
|
||||
default: return ERR_JSON_SERIALISE;
|
||||
}
|
||||
|
@ -1,71 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -5,14 +5,12 @@
|
||||
|
||||
#include "world.h"
|
||||
|
||||
static char const *const WORLD_JSON_FMT = "{so, so, sI, sI, sI}";
|
||||
static char const *const WORLD_JSON_FMT = "{so, so, 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;
|
||||
|
||||
@ -86,7 +84,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 tick, size_t height, size_t width,
|
||||
size_t height, size_t width,
|
||||
struct json_t **json
|
||||
) {
|
||||
assert(entities_json != NULL);
|
||||
@ -96,7 +94,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,
|
||||
"tick", tick, "height", height, "width", width
|
||||
"height", height, "width", width
|
||||
);
|
||||
|
||||
if (world_json == NULL) return ERR_JSON_SERIALISE;
|
||||
@ -133,7 +131,7 @@ enum error_t world_serialise(
|
||||
err = world_serialise_parts(
|
||||
entities_json,
|
||||
registered_entities_json,
|
||||
self->tick, self->height, self->width,
|
||||
self->height, self->width,
|
||||
&world_json
|
||||
);
|
||||
if (err != ERR_OK) goto error_world;
|
||||
@ -152,7 +150,7 @@ error:
|
||||
static enum error_t world_deserialise_parts(
|
||||
struct json_t **entities_json,
|
||||
struct json_t **registered_entities_json,
|
||||
size_t *tick, size_t *height, size_t *width,
|
||||
size_t *height, size_t *width,
|
||||
struct json_t *json
|
||||
) {
|
||||
assert(entities_json != NULL);
|
||||
@ -164,7 +162,7 @@ static enum error_t world_deserialise_parts(
|
||||
int err = json_unpack(json, WORLD_JSON_FMT,
|
||||
"entities", entities_json,
|
||||
"registered-entities", registered_entities_json,
|
||||
"tick", tick, "height", height, "width", width
|
||||
"height", height, "width", width
|
||||
);
|
||||
|
||||
if (err < 0) return ERR_JSON_DESERIALISE;
|
||||
@ -232,7 +230,7 @@ enum error_t world_deserialise(struct world_t *self, struct json_t *json) {
|
||||
enum error_t err = world_deserialise_parts(
|
||||
&entities_json,
|
||||
®istered_entities_json,
|
||||
&temp_world.tick, &temp_world.height, &temp_world.width,
|
||||
&temp_world.height, &temp_world.width,
|
||||
json
|
||||
);
|
||||
if (err != ERR_OK) return err;
|
||||
|
17
doc/TODO.gmi
17
doc/TODO.gmi
@ -2,18 +2,12 @@
|
||||
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
|
||||
@ -25,13 +19,14 @@ A list of things I'd like to accomplish
|
||||
* Write Makefile to automate compilation
|
||||
|
||||
## Planned
|
||||
* Requests/responses for more than just the complete set of world data all at once
|
||||
* Split game ticking and socket connecting logic (can only tick on connection atm)
|
||||
* 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
|
||||
|
@ -1,3 +0,0 @@
|
||||
-- init.lua
|
||||
|
||||
print("Hello, world!")
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "core",
|
||||
"version": "0.0.1",
|
||||
"depends": [],
|
||||
"optdepends": []
|
||||
}
|
@ -7,40 +7,16 @@ 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 ${server_args} &
|
||||
./build/server/simworld-daemon &
|
||||
sleep 1
|
||||
|
||||
echo "### CLIENT 1 ###"
|
||||
./build/client/simworld-client ${client1_args}
|
||||
./build/client/simworld-client
|
||||
|
||||
echo "### CLIENT 2 ###"
|
||||
./build/client/simworld-client ${client2_args}
|
||||
|
||||
echo "### CLIENT 3 ###"
|
||||
sleep 1
|
||||
./build/client/simworld-client ${client3_args}
|
||||
./build/client/simworld-client
|
||||
|
||||
killall simworld-daemon
|
||||
|
@ -9,5 +9,3 @@ file(GLOB_RECURSE SOURCES ${SOURCE_DIR}/*.c)
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE simworld)
|
||||
|
||||
install(TARGETS ${PROJECT_NAME})
|
||||
|
16
server/src/data.h
Normal file
16
server/src/data.h
Normal file
@ -0,0 +1,16 @@
|
||||
#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
|
@ -1,67 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
#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
|
@ -1,6 +1,5 @@
|
||||
// main.c
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
@ -13,48 +12,61 @@
|
||||
#include <request.h>
|
||||
|
||||
#include "opts.h"
|
||||
#include "socket.h"
|
||||
#include "game.h"
|
||||
#include "sock.h"
|
||||
#include "data.h"
|
||||
|
||||
static void handle_signal(int signal_no) {
|
||||
(void)signal_no;
|
||||
|
||||
remove(SOCKET_PATH);
|
||||
remove(SOCK_PATH);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
static enum error_t simulation_thread_body(struct game_t *game) {
|
||||
assert(game != NULL);
|
||||
enum error_t game_loop(struct data_t *data) {
|
||||
enum error_t err = ERR_OK;
|
||||
|
||||
// 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;
|
||||
// TEMP
|
||||
struct response_body_get_world_data_t response_body = { data->world };
|
||||
struct response_t response = { true, REQUEST_GET_WORLD_DATA, &response_body };
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
char const *response_json_str = NULL;
|
||||
err = response_serialise_str(&response, &response_json_str);
|
||||
if (err) return err;
|
||||
|
||||
return ERR_OK;
|
||||
printf("serialised response: %s\n", response_json_str);
|
||||
|
||||
error:
|
||||
return ERR_MUTEX;
|
||||
}
|
||||
struct response_t response2 = { 0 };
|
||||
err = response_deserialise_str(&response2, response_json_str);
|
||||
if (err) return err;
|
||||
|
||||
struct response_body_get_world_data_t *response2_body = response2.body;
|
||||
|
||||
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
|
||||
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
|
||||
);
|
||||
if (err != 0) return ERR_THREAD;
|
||||
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);
|
||||
|
||||
return ERR_OK;
|
||||
}
|
||||
@ -63,8 +75,7 @@ static enum error_t simulation_thread(
|
||||
int main(int argc, char **argv) {
|
||||
// Set up variables
|
||||
enum error_t err = ERR_OK;
|
||||
struct game_t data = { 0 };
|
||||
|
||||
struct data_t data = { 0 };
|
||||
|
||||
// Signal handling; TODO: should probably improve this
|
||||
signal(SIGINT, handle_signal);
|
||||
@ -83,13 +94,6 @@ 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;
|
||||
|
||||
@ -102,34 +106,16 @@ 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 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;
|
||||
// Socket handling and run gameloop
|
||||
err = sock_loop(&data, game_loop);
|
||||
if (err) goto handle_error;
|
||||
|
||||
// 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:
|
||||
@ -137,6 +123,6 @@ handle_error:
|
||||
opts_free(&data.options);
|
||||
|
||||
handle_error_pre_opts:
|
||||
write_log(data.options.daemonise, "SERVER ERROR: %s\n", ERROR_STRS[err]);
|
||||
printf("SERVER ERROR: %s\n", ERROR_STRS[err]);
|
||||
return err;
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/syslog.h>
|
||||
|
||||
#include <getopt.h>
|
||||
@ -12,29 +11,22 @@
|
||||
|
||||
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 }
|
||||
{ "daemon", no_argument, 0, 'd' },
|
||||
};
|
||||
|
||||
while (true) {
|
||||
int option_index = 0;
|
||||
|
||||
int c = getopt_long(argc, argv, "dm:", long_options, &option_index);
|
||||
int c = getopt_long(argc, argv, "d", 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;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include <error.h>
|
||||
|
||||
struct options_t {
|
||||
char* mods_directory;
|
||||
bool daemonise;
|
||||
};
|
||||
|
||||
|
@ -1,63 +1,3 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,8 @@
|
||||
#ifndef REQUEST_H
|
||||
#define REQUEST_H
|
||||
|
||||
#include <request.h>
|
||||
#include <error.h>
|
||||
|
||||
#include "game.h"
|
||||
|
||||
struct response_t handle_request(
|
||||
struct request_t const *,
|
||||
struct game_t *
|
||||
);
|
||||
enum error_t handle_request(void);
|
||||
|
||||
#endif
|
||||
|
74
server/src/sock.c
Normal file
74
server/src/sock.c
Normal file
@ -0,0 +1,74 @@
|
||||
// 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;
|
||||
}
|
17
server/src/sock.h
Normal file
17
server/src/sock.h
Normal file
@ -0,0 +1,17 @@
|
||||
#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
|
@ -1,125 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
#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
|
Loading…
Reference in New Issue
Block a user