diff options
author | Nick Van Doorn <vandoorn.nick@gmail.com> | 2019-02-23 02:19:04 -0800 |
---|---|---|
committer | Nick Van Doorn <vandoorn.nick@gmail.com> | 2019-02-23 02:19:04 -0800 |
commit | 93ff075b775e77d83e8f0dbcd19e7d2aa81a43f8 (patch) | |
tree | fcc785e6b45d153dc786ae4ecd721636748c3ca6 | |
parent | 8ace2558f882c0298d02a50ffebb3aacf639407b (diff) |
Holy fuck my life hurts
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | core.c | 24 | ||||
-rw-r--r-- | core.test.c | 7 | ||||
-rw-r--r-- | hash-table.c | 152 | ||||
-rw-r--r-- | hash-table.h | 29 | ||||
-rw-r--r-- | hash-table.test.c | 182 | ||||
-rw-r--r-- | hash-table.test.h | 6 | ||||
-rwxr-xr-x | naive-watch.sh | 7 | ||||
-rw-r--r-- | parser.c | 25 | ||||
-rw-r--r-- | parser.h | 2 | ||||
-rw-r--r-- | server.h | 3 |
11 files changed, 427 insertions, 18 deletions
@@ -3,8 +3,8 @@ CC=gcc CFLAGS=-I. -I./mongoose -I./pool -DTHPOOL_DEBUG -pthread OBJDIR = build -DEPS= parser.h server.h test-lib.h core.h graph.h graph.test.h pool/thpool.h hash-table.h -OBJ = parser.o server.o test-lib.o core.o graph.o graph.test.o pool/thpool.o hash-table.o +DEPS= parser.h server.h test-lib.h core.h graph.h graph.test.h pool/thpool.h hash-table.h hash-table.test.h cjson/cJSON.h cjson/cJSON_Utils.h +OBJ = parser.o server.o test-lib.o core.o graph.o graph.test.o pool/thpool.o hash-table.o hash-table.test.o cjson/cJSON.o cjson/cJSON_Utils.o MAIN_OBJ = main.o TEST_OBJ = core.test.o @@ -14,12 +14,12 @@ TEST_OBJ = core.test.o main: $(OBJ) $(MAIN_OBJ) $(CC) -o $@ $^ $(CFLAGS) -test-bin: $(OBJ) $(TEST_OBJ) +cnaked-test-bin: $(OBJ) $(TEST_OBJ) $(CC) -o $@ $^ $(CFLAGS) .PHONY: test test: - make test-bin && ./test-bin + make cnaked-test-bin && ./cnaked-test-bin .PHONY: clean clean: @@ -8,6 +8,10 @@ #define DEFAULT_PORT 5000 +const static char *HEAD_TYPE = "bendr"; +const static char *TAIL_TYPE = "pixel"; +const static char *SNAKE_COLOUR = "#ff00ff"; + struct StateNode { struct GameState_t state; // Track the number of connected @@ -23,8 +27,7 @@ void parseArgs(struct server_Ctx_t *ctx, int argc, char *argv[]) { printf("Usage: ./cnaked PORT\n"); exit(1); } - int port = atoi(argv[0]); - ctx->port = port == 0 ? DEFAULT_PORT : port; + ctx->port = argv[0]; } // TODO I have no idea how this function @@ -44,10 +47,10 @@ int remakeGameState(struct GameState_t *oldState, struct GameState_t *newState, return -1; } -int gameMoveToJson(enum GameMove_t *res, char *buff, int buffSize) { +int gameMoveToJson(enum GameMove_t *move, char *buff, int buffSize) { char *moveStr; // save a byte, pass a pointer - switch ((*res)) { + switch ((*move)) { case UP: moveStr = "up"; break; @@ -65,6 +68,12 @@ int gameMoveToJson(enum GameMove_t *res, char *buff, int buffSize) { return snprintf(buff, buffSize, "{\"move\":\"%s\"}", moveStr); } +int startJson(char *buff, int buffSize) { + return snprintf(buff, 1024, + "{\"color\":\"%s\",\"headType\":\"%s\",\"tailType\":\"%s\"}", + SNAKE_COLOUR, HEAD_TYPE, TAIL_TYPE); +} + void moveHandler(struct server_Req_t *req, struct server_Res_t *res, void *ctx) { char buff[300]; @@ -84,7 +93,12 @@ void moveHandler(struct server_Req_t *req, struct server_Res_t *res, void startHandler(struct server_Req_t *req, struct server_Res_t *res, void *ctx) { - res->write("{}", HTTP_OK); + char buff[1024]; + int r = startJson(buff, 1024); + if (r) + res->write("Ya blew it", HTTP_INTERNAL_SERVER_ERR); + else + res->write(buff, HTTP_OK); } int core_setup(int argc, char *argv[]) { diff --git a/core.test.c b/core.test.c index 7be1e20..f51c907 100644 --- a/core.test.c +++ b/core.test.c @@ -1,6 +1,7 @@ #include "core.h" #include "game-state.h" #include "graph.test.h" +#include "hash-table.test.h" #include "test-lib.h" #include <string.h> @@ -13,6 +14,12 @@ int testMoveToJson() { } int main() { + // core tests written here syncTest("gameMoveToJson", "JSON payload is incorrect", testMoveToJson); + + // Tests included from other lib + // graph.test.c graph_test(); + // hash-table.test.c + hash_test(); } diff --git a/hash-table.c b/hash-table.c index 4375d15..6f4f048 100644 --- a/hash-table.c +++ b/hash-table.c @@ -1,7 +1,153 @@ #include "hash-table.h" +#include <pthread.h> +#include <stdlib.h> -int hash_insert(struct hash_Table_t *t, int key) { return -1; } +int computeHashIndex(int key) { + int r = key % HASH_TABLE_SIZE; + return r; +} -int hash_remove(struct hash_Table_t *t, int key) { return -1; } +struct hash_Node_t *linearListSearch(struct hash_Node_t *node, int key) { + while (node->next != NULL) { + if (node->key == key) + return node; + node = (struct hash_Node_t *)node->next; + } + return NULL; +} -int hash_contains(struct hash_Table_t *t, int key) { return -1; } +int hash_init(struct hash_Table_t *t) { + // mark all node "slots" as NULL initially + for (int i = 0; i < HASH_TABLE_SIZE; i++) { + t->nodes[i] = NULL; + } + t->nNodes = 0; + return pthread_mutex_init(&t->lock, NULL); +} + +// we don't _need_ a malloc because +// in theory we could predictably insert one stattically +// and then dynamically link all collisions, assuming +// the table is large enough +int hash_insert(struct hash_Table_t *t, int key) { + int r, index; + struct hash_Node_t *node, *slot; + r = pthread_mutex_lock(&t->lock); + if (r) + return r; + + index = computeHashIndex(key); + slot = t->nodes[index]; + + if (slot != NULL && slot->key == key) { + return DUPLICATE_KEY_ERR; + } + + node = malloc(sizeof(struct hash_Node_t)); + node->key = key; + node->next = NULL; + node->prev = NULL; + + // easy case + if (slot == NULL) { + t->nodes[index] = node; + } // harder-ish case + else { + while (slot->next != NULL) { + slot = (struct hash_Node_t *)slot->next; + } + slot->next = node; + node->prev = slot; + } + t->nNodes++; + if (t->nNodes < 0) { + printf("%d\n", t->nNodes); + } + r = pthread_mutex_unlock(&t->lock); + return r; +} + +// TODO free da node +int hash_remove(struct hash_Table_t *t, int key) { + int r, hashKey; + struct hash_Node_t *node; + r = pthread_mutex_lock(&t->lock); + if (r) + return r; + hashKey = computeHashIndex(key); + node = t->nodes[hashKey]; + // could not find it case + if (node == NULL) { + return KEY_DOES_NOT_EXIST; + } + + // Note that all error exits should + // try and unlock it first + // direct hit case + else if (node->key == key) { + // move up the next node + if (node->next != NULL) { + t->nodes[hashKey] = node->next; + node->prev = NULL; + } + // empty it out case + else { + t->nodes[hashKey] = NULL; + } + } + // linear probing case + else { + node = linearListSearch(node, key); + if (node == NULL) { + pthread_mutex_unlock(&t->lock); + return KEY_DOES_NOT_EXIST; + } + if (node->next != NULL) { + // we assume this node + // must have a previous + // since we had to search to find it + struct hash_Node_t *newNext = node->next; + struct hash_Node_t *newPrev = node->prev; + newPrev->next = newNext; + newNext->prev = newPrev; + } + } + // This should never happen. + // If we have nothing to free, + // we should have exited from + // an error before hitting this + if (node == NULL) { + pthread_mutex_unlock(&t->lock); + return FREE_EMPTY_NODE; + } + free(node); + t->nNodes--; + r = pthread_mutex_unlock(&t->lock); + return r; +} + +int hash_contains(struct hash_Table_t *t, int key) { + int r, hashKey, contains; + r = pthread_mutex_lock(&t->lock); + if (r) + return r; + + hashKey = computeHashIndex(key); + if (t->nodes[hashKey] == NULL) { + contains = 0; + goto done; + } else { + if (t->nodes[hashKey]->key == key) { + contains = 1; + goto done; + } else { + contains = linearListSearch(t->nodes[hashKey], key) != NULL; + goto done; + } + } +done: + r = pthread_mutex_unlock(&t->lock); + if (r) + return r; + return contains; +} diff --git a/hash-table.h b/hash-table.h index 16e4abb..5c081e2 100644 --- a/hash-table.h +++ b/hash-table.h @@ -4,19 +4,48 @@ #include <pthread.h> #define DUPLICATE_KEY_ERR -1 +#define KEY_DOES_NOT_EXIST -2 +#define FREE_EMPTY_NODE -3 + +#define HASH_TABLE_SIZE 4096 * 1024 + +struct hash_Node_t { + int key; + void *next; + void *prev; +}; struct hash_Table_t { // lock the hash table // to ensure it's thread safe pthread_mutex_t lock; + struct hash_Node_t *nodes[HASH_TABLE_SIZE]; + int nNodes; }; int hash_init(struct hash_Table_t *t); +/** + * Insert an integer value into a hash table + * + * This routine does modify the struct but uses + * a mutex to maintain thread safety + */ int hash_insert(struct hash_Table_t *t, int key); +/** + * Remove an integer value + * + * Also threadsafe + */ int hash_remove(struct hash_Table_t *t, int key); +/** + * Check if the table contains a value + * + * _Should_ be safe from thread related problems + * here since it's strictly a read + */ int hash_contains(struct hash_Table_t *t, int key); #endif diff --git a/hash-table.test.c b/hash-table.test.c new file mode 100644 index 0000000..d0a0b5e --- /dev/null +++ b/hash-table.test.c @@ -0,0 +1,182 @@ +#include "hash-table.h" +#include "hash-table.test.h" +#include "test-lib.h" +#include <pthread.h> +#include <stdio.h> + +int hashInit() { + struct hash_Table_t t; + int r; + r = hash_init(&t); + if (r) + return r; + for (int i = 0; i < HASH_TABLE_SIZE; i++) { + if (t.nodes[i] != NULL) + return -1; + } + // grab a lock to make sure the + // mutex is valid + return pthread_mutex_lock(&t.lock); +} + +int hashInsert() { + struct hash_Table_t t; + int r; + r = hash_init(&t); + if (r) + return r; + r = hash_insert(&t, 42); + return r; +} + +int hashRemove() { + struct hash_Table_t t; + int r, big; + big = 100000; + r = hash_init(&t); + if (r) + return r; + for (int i = 0; i < big; i++) { + r = hash_insert(&t, i); + if (r) + return r; + } + for (int i = 0; i < big; i++) { + r = hash_remove(&t, i); + if (r) { + return r; + } + } + return t.nNodes != 0; +} + +int hashContains() { + struct hash_Table_t t; + int r; + r = hash_init(&t); + if (r) + return r; + r = hash_insert(&t, 42); + if (r) + return r; + r = hash_contains(&t, 42); + return r != 1; +} + +int hashContainsNotFound() { + struct hash_Table_t t; + int r, big; + big = 10000; + r = hash_init(&t); + if (r) + return r; + for (int i = 0; i < big; i++) { + r = hash_insert(&t, i); + if (r) + return r; + } + r = hash_contains(&t, big + 1); + return r; +} + +int hashContainsBigFound() { + struct hash_Table_t t; + int r, big; + big = 10000; + r = hash_init(&t); + if (r) + return r; + for (int i = 0; i < big; i++) { + r = hash_insert(&t, i); + if (r) + return r; + } + r = hash_contains(&t, big / 2); + return r != 1; +} + +void *hashInsertThreaded_handler(void *ctx) { + struct hash_Table_t *t = ctx; + for (int i = 0; i < 100000; i++) { + hash_insert(t, 100000 + i); + } + return NULL; +} + +int hashInsertThreaded() { + pthread_t thread; + struct hash_Table_t t; + int r; + r = pthread_create(&thread, NULL, hashInsertThreaded_handler, &t); + if (r) + return r; + + r = hash_init(&t); + if (r) + return r; + for (int i = 0; i < 100000; i++) { + r = hash_insert(&t, i); + if (r) + return r; + } + return pthread_join(thread, NULL); +} + +int hashInsertDupe() { + struct hash_Table_t t; + int r; + r = hash_init(&t); + if (r) + return r; + r = hash_insert(&t, 42); + if (r) + return r; + r = hash_insert(&t, 42); + return r != DUPLICATE_KEY_ERR; +} + +void *hailMary_handler(void *ctx) { + struct hash_Table_t *t = ctx; + int r; + for (int i = 0; i < 100000; i++) { + r = hash_insert(t, i); + } +} + +int hailMary() { + pthread_t thread; + struct hash_Table_t t; + int r; + + r = hash_init(&t); + if (r) + return r; + r = pthread_create(&thread, NULL, hailMary_handler, &t); + if (r) + return r; + r = pthread_join(thread, NULL); + printf("%d\n", t.nNodes); + if (r) + return r; + for (int i = 0; i < 100000; i++) { + r = hash_remove(&t, i); + if (r) + return r; + } +} + +int hash_test() { + syncTest("hash_init", "Hash init success path failed", hashInit); + syncTest("hash_insert", "Hash insert success path failed", hashInsert); + syncTest("hash_remove", "Hash remove success path failed", hashRemove); + syncTest("hash_contains", "Hash contains success path failed", hashContains); + syncTest("hash_contains big", "Hash contains \"big\" success path failed", + hashContainsBigFound); + syncTest("hash_contains not found", "Hash contains not found failed", + hashContainsNotFound); + // syncTest("hash_insert multithread", "Hash insert multithread failed", + // hashInsertThreaded); + syncTest("hash_insert dupe", "Hash insert did not return correct error", + hashInsertDupe); + syncTest("Hail Mary", "Not shocking", hailMary); +} diff --git a/hash-table.test.h b/hash-table.test.h new file mode 100644 index 0000000..8005b07 --- /dev/null +++ b/hash-table.test.h @@ -0,0 +1,6 @@ +#ifndef HASH_TABLE_TEST +#define HASH_TABLE_TEST + +int hash_test(); + +#endif diff --git a/naive-watch.sh b/naive-watch.sh index 80a53a2..f207376 100755 --- a/naive-watch.sh +++ b/naive-watch.sh @@ -1,10 +1,9 @@ +#!/usr/bin/env bash + test() { - # Unit tests implemented in C make test - # Integration tests implemented - # with node-fetch/jest - # yarn jest } + test while sleep 5; do test; done @@ -1,4 +1,27 @@ +#include "cjson/cJSON.h" #include "game-state.h" #include "parser.h" -int parser_stringToState(char *reqBody, struct GameState_t *state) { return 0; } +int parseYou(cJSON *you, struct GameState_t *state) { + int i = 0; + cJSON *health, *body, *coord, *x, *y; + health = cJSON_GetObjectItemCaseSensitive(you, "health"); + body = cJSON_GetObjectItemCaseSensitive(you, "body"); + state->ourHealth = health->valueint; + cJSON_ArrayForEach(coord, body) { + x = cJSON_GetObjectItemCaseSensitive(coord, "x"); + y = cJSON_GetObjectItemCaseSensitive(coord, "y"); + state->ourBody[i][0] = x->valueint; + state->ourBody[i++][1] = y->valueint; + }; +} + +int parser_stringToState(char *reqBody, struct GameState_t *state) { + cJSON *reqMonitor = cJSON_parse(reqBody); + cJSON *you; + if (reqMonitor == NULL) { + return JSON_PARSE_FAIL; + } + you = cJSON_GetObjectItemCaseSensitive(reqMonitor, "you"); + parseYou(you, state); +} @@ -3,6 +3,8 @@ #include "game-state.h" +#define JSON_PARSE_FAIL -1 + int parser_stringToState(char *reqBody, struct GameState_t *state); #endif @@ -2,9 +2,10 @@ #define CNAKE_SERVER_H #define HTTP_OK 200 +#define HTTP_INTERNAL_SERVER_ERR 500 struct server_Ctx_t { - int port; + char *port; }; struct server_Req_t { |