Prog
This commit is contained in:
@ -7,7 +7,6 @@
|
||||
target_link_libraries(${DUSK_TARGET_NAME}
|
||||
PUBLIC
|
||||
m
|
||||
raylib
|
||||
)
|
||||
|
||||
# Includes
|
||||
@ -20,14 +19,10 @@ target_include_directories(${DUSK_TARGET_NAME}
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
main.c
|
||||
input.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(assert)
|
||||
add_subdirectory(console)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(game)
|
||||
add_subdirectory(network)
|
||||
add_subdirectory(rpg)
|
||||
add_subdirectory(util)
|
@ -7,12 +7,6 @@
|
||||
|
||||
#include "assert.h"
|
||||
|
||||
pthread_t assertMainThread = 0;
|
||||
|
||||
void assertInit() {
|
||||
assertMainThread = pthread_self();
|
||||
}
|
||||
|
||||
void assertTrueImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
|
@ -8,13 +8,6 @@
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
extern pthread_t assertMainThread;
|
||||
|
||||
/**
|
||||
* Initialises the assert system.
|
||||
*/
|
||||
void assertInit();
|
||||
|
||||
/**
|
||||
* Assert a given value to be true.
|
||||
*
|
||||
@ -135,10 +128,4 @@ void assertMemoryRangeMatchesImpl(
|
||||
#define assertStrLenMin(str, len, message) \
|
||||
assertTrue(strlen(str) >= len, message)
|
||||
|
||||
#define assertIsMainThread(message) \
|
||||
assertTrue(pthread_self() == assertMainThread, message)
|
||||
|
||||
#define assertNotMainThread(message) \
|
||||
assertFalse(pthread_self() == assertMainThread, message)
|
||||
|
||||
// EOF
|
@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
console.c
|
||||
consolecmd.c
|
||||
consolevar.c
|
||||
)
|
@ -1,395 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "console.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "input.h"
|
||||
|
||||
console_t CONSOLE;
|
||||
|
||||
void consoleInit() {
|
||||
memoryZero(&CONSOLE, sizeof(console_t));
|
||||
pthread_mutex_init(&CONSOLE.lock, NULL); // Initialize the mutex
|
||||
|
||||
// Register the get and set command.
|
||||
CONSOLE.cmdGet = consoleRegCmd("get", cmdGet);
|
||||
CONSOLE.cmdSet = consoleRegCmd("set", cmdSet);
|
||||
consoleRegCmd("echo", cmdEcho);
|
||||
|
||||
consolePrint(" = Dawn Console = ");
|
||||
}
|
||||
|
||||
consolecmd_t * consoleRegCmd(const char_t *name, consolecmdfunc_t function) {
|
||||
pthread_mutex_lock(&CONSOLE.lock); // Lock
|
||||
consolecmd_t *cmd = &CONSOLE.commands[CONSOLE.commandCount++];
|
||||
consoleCmdInit(cmd, name, function);
|
||||
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
|
||||
return cmd;
|
||||
}
|
||||
|
||||
consolevar_t * consoleRegVar(
|
||||
const char_t *name,
|
||||
const char_t *value,
|
||||
consolevarchanged_t event
|
||||
) {
|
||||
pthread_mutex_lock(&CONSOLE.lock); // Lock
|
||||
consolevar_t *var = &CONSOLE.variables[CONSOLE.variableCount++];
|
||||
consoleVarInitListener(var, name, value, event);
|
||||
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
|
||||
return var;
|
||||
}
|
||||
|
||||
void consolePrint(const char_t *message, ...) {
|
||||
char_t buffer[CONSOLE_LINE_MAX];
|
||||
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args);
|
||||
va_end(args);
|
||||
|
||||
// Move all lines back
|
||||
memoryMove(
|
||||
CONSOLE.line[0],
|
||||
CONSOLE.line[1],
|
||||
(CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX
|
||||
);
|
||||
|
||||
// Copy the new line
|
||||
memoryCopy(
|
||||
CONSOLE.line[CONSOLE_HISTORY_MAX - 1],
|
||||
buffer,
|
||||
len + 1
|
||||
);
|
||||
printf("%s\n", buffer);
|
||||
}
|
||||
|
||||
void consoleExec(const char_t *line) {
|
||||
pthread_mutex_lock(&CONSOLE.lock); // Lock
|
||||
assertNotNull(line, "line must not be NULL");
|
||||
assertTrue(
|
||||
CONSOLE.execBufferCount < CONSOLE_EXEC_BUFFER_MAX,
|
||||
"Too many commands in the buffer."
|
||||
);
|
||||
|
||||
char_t buffer[CONSOLE_LINE_MAX];
|
||||
size_t i = 0, j = 0;
|
||||
char_t c;
|
||||
consoleexecstate_t state = CONSOLE_EXEC_STATE_INITIAL;
|
||||
consolecmdexec_t *exec = NULL;
|
||||
|
||||
while(state != CONSOLE_EXEC_STATE_FULLY_PARSED) {
|
||||
c = line[i];
|
||||
|
||||
switch(state) {
|
||||
case CONSOLE_EXEC_STATE_INITIAL:
|
||||
assertTrue(j == 0, "Buffer not empty?");
|
||||
|
||||
if(c == '\0') {
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
break;
|
||||
}
|
||||
|
||||
if(stringIsWhitespace(c) || c == ';') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
state = CONSOLE_EXEC_STATE_PARSE_CMD;
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_PARSE_CMD:
|
||||
if(stringIsWhitespace(c) || c == '\0' || c == ';') {
|
||||
state = CONSOLE_EXEC_STATE_CMD_PARSED;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c == '"') {
|
||||
// Can't handle quotes within the command.
|
||||
consolePrint("Invalid command");
|
||||
while(c != '\0' && c != ';') c = line[++i];
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer[j++] = c;
|
||||
i++;
|
||||
|
||||
if(j >= CONSOLE_LINE_MAX) {
|
||||
consolePrint("Command too long");
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_CMD_PARSED:
|
||||
if(j == 0) {
|
||||
state = CONSOLE_EXEC_STATE_INITIAL;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create exec
|
||||
assertNull(exec, "Existing command parsing?");
|
||||
|
||||
exec = &CONSOLE.execBuffer[CONSOLE.execBufferCount];
|
||||
memoryZero(exec, sizeof(consolecmdexec_t));
|
||||
|
||||
buffer[j] = '\0';
|
||||
stringCopy(exec->command, buffer, CONSOLE_LINE_MAX);
|
||||
state = CONSOLE_EXEC_STATE_FIND_ARG;
|
||||
|
||||
j = 0;// Free up buffer
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_FIND_ARG:
|
||||
if(c == '\0' || c == ';') {
|
||||
state = CONSOLE_EXEC_STATE_CMD_FINISHED;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(stringIsWhitespace(c)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c == '"') {
|
||||
state = CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED;
|
||||
i++;
|
||||
} else {
|
||||
state = CONSOLE_EXEC_STATE_PARSE_ARG;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_PARSE_ARG:
|
||||
if(stringIsWhitespace(c) || c == '\0' || c == ';') {
|
||||
state = CONSOLE_EXEC_STATE_ARG_PARSED;
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer[j++] = c;
|
||||
i++;
|
||||
|
||||
if(j >= CONSOLE_LINE_MAX) {
|
||||
consolePrint("Arg too long");
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED:
|
||||
if(c == '"') {
|
||||
state = CONSOLE_EXEC_STATE_ARG_PARSED;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c == '\0' || c == ';') {
|
||||
consolePrint("Unterminated quote");
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c == '\\') {
|
||||
c = line[++i];
|
||||
|
||||
if(c == '\0' || c == ';') {
|
||||
consolePrint("Unterminated quote");
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
buffer[j++] = c;
|
||||
i++;
|
||||
|
||||
if(j >= CONSOLE_LINE_MAX) {
|
||||
consolePrint("Arg too long");
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_ARG_PARSED:
|
||||
buffer[j] = '\0';
|
||||
stringCopy(exec->argv[exec->argc++], buffer, CONSOLE_LINE_MAX);
|
||||
state = CONSOLE_EXEC_STATE_FIND_ARG;
|
||||
j = 0;// Free up buffer
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_CMD_FINISHED:
|
||||
assertNotNull(exec, "No command found?");
|
||||
|
||||
// Now, is there a command that matches?
|
||||
for(uint32_t k = 0; k < CONSOLE.commandCount; k++) {
|
||||
consolecmd_t *cmd = &CONSOLE.commands[k];
|
||||
if(stringCompare(cmd->name, exec->command) != 0) continue;
|
||||
exec->cmd = cmd;
|
||||
break;
|
||||
}
|
||||
|
||||
if(exec->cmd == NULL) {
|
||||
// Command wasn't found, is there a variable that matches?
|
||||
for(uint32_t k = 0; k < CONSOLE.variableCount; k++) {
|
||||
consolevar_t *var = &CONSOLE.variables[k];
|
||||
if(stringCompare(var->name, exec->command) != 0) continue;
|
||||
|
||||
// Matching variable found, is this a GET or a SET?
|
||||
if(exec->argc == 0) {
|
||||
exec->cmd = CONSOLE.cmdGet;
|
||||
stringCopy(exec->argv[0], exec->command, CONSOLE_LINE_MAX);
|
||||
exec->argc = 1;
|
||||
} else {
|
||||
exec->cmd = CONSOLE.cmdSet;
|
||||
stringCopy(exec->argv[1], exec->argv[0], CONSOLE_LINE_MAX);
|
||||
stringCopy(exec->argv[0], exec->command, CONSOLE_LINE_MAX);
|
||||
exec->argc = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(exec->cmd == NULL) {
|
||||
consolePrint("Command not found", exec->command);
|
||||
exec = NULL;
|
||||
state = CONSOLE_EXEC_STATE_INITIAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prep for next command.
|
||||
exec = NULL;
|
||||
state = CONSOLE_EXEC_STATE_INITIAL;
|
||||
CONSOLE.execBufferCount++;
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid state.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
|
||||
}
|
||||
|
||||
void consoleProcess() {
|
||||
pthread_mutex_lock(&CONSOLE.lock); // Lock
|
||||
for(uint32_t i = 0; i < CONSOLE.execBufferCount; i++) {
|
||||
consolecmdexec_t *exec = &CONSOLE.execBuffer[i];
|
||||
|
||||
assertNotNull(exec->cmd, "Command execution has no command.");
|
||||
|
||||
exec->cmd->function(exec);
|
||||
}
|
||||
|
||||
// Clear the exec buffer
|
||||
CONSOLE.execBufferCount = 0;
|
||||
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
|
||||
}
|
||||
|
||||
void cmdGet(const consolecmdexec_t *exec) {
|
||||
assertTrue(
|
||||
exec->argc >= 1,
|
||||
"Get command requires 1 argument."
|
||||
);
|
||||
|
||||
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
|
||||
consolevar_t *var = &CONSOLE.variables[i];
|
||||
if(stringCompare(var->name, exec->argv[0]) != 0) continue;
|
||||
consolePrint("%s", var->value);
|
||||
return;
|
||||
}
|
||||
|
||||
consolePrint("Error: Variable '%s' not found.", exec->argv[0]);
|
||||
}
|
||||
|
||||
void cmdSet(const consolecmdexec_t *exec) {
|
||||
assertTrue(exec->argc >= 2, "set command requires 2 arguments.");
|
||||
|
||||
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
|
||||
consolevar_t *var = &CONSOLE.variables[i];
|
||||
if(stringCompare(var->name, exec->argv[0]) != 0) continue;
|
||||
consoleVarSetValue(var, exec->argv[1]);
|
||||
consolePrint("%s %s", var->name, var->value);
|
||||
for(i = 0; i < var->eventCount; i++) {
|
||||
assertNotNull(var->events[i], "Event is NULL");
|
||||
var->events[i](var);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
consolePrint("Error: Variable '%s' not found.", exec->argv[0]);
|
||||
}
|
||||
|
||||
void cmdEcho(const consolecmdexec_t *exec) {
|
||||
assertTrue(
|
||||
exec->argc >= 1,
|
||||
"echo command requires 1 argument."
|
||||
);
|
||||
|
||||
consolePrint("%s", exec->argv[0]);
|
||||
}
|
||||
|
||||
// May move these later
|
||||
void consoleUpdate() {
|
||||
if(inputIsPressed(INPUT_TOGGLE_CONSOLE)) {
|
||||
CONSOLE.open = !CONSOLE.open;
|
||||
} else if(CONSOLE.open) {
|
||||
switch(INPUT.keyPressed) {
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case KEY_ENTER:
|
||||
consoleExec(CONSOLE.inputBuffer);
|
||||
CONSOLE.inputIndex = 0;
|
||||
CONSOLE.inputBuffer[0] = '\0';
|
||||
break;
|
||||
|
||||
case KEY_BACKSPACE:
|
||||
if(CONSOLE.inputIndex > 0) {
|
||||
CONSOLE.inputIndex--;
|
||||
CONSOLE.inputBuffer[CONSOLE.inputIndex] = '\0';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if(
|
||||
INPUT.keyPressed >= 32 &&
|
||||
INPUT.keyPressed <= 126 &&
|
||||
CONSOLE.inputIndex < CONSOLE_LINE_MAX - 1
|
||||
) {
|
||||
CONSOLE.inputBuffer[CONSOLE.inputIndex++] = INPUT.charPressed;
|
||||
CONSOLE.inputBuffer[CONSOLE.inputIndex] = '\0';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
consoleProcess();
|
||||
}
|
||||
|
||||
void consoleDraw() {
|
||||
if(!CONSOLE.open) return;
|
||||
|
||||
size_t i = 0;
|
||||
char_t *line;
|
||||
int32_t fontSize = 10;
|
||||
do {
|
||||
line = CONSOLE.line[i];
|
||||
if(line[0] == '\0') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
DrawText(line, 0, i*fontSize, fontSize, YELLOW);
|
||||
i++;
|
||||
} while(i < CONSOLE_HISTORY_MAX);
|
||||
|
||||
DrawText(
|
||||
CONSOLE.inputBuffer, 0,
|
||||
(CONSOLE_HISTORY_MAX + 1) * fontSize,
|
||||
fontSize,
|
||||
PINK
|
||||
);
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "consolevar.h"
|
||||
#include "consolecmd.h"
|
||||
|
||||
typedef enum {
|
||||
CONSOLE_EXEC_STATE_INITIAL,
|
||||
CONSOLE_EXEC_STATE_PARSE_CMD,
|
||||
CONSOLE_EXEC_STATE_CMD_PARSED,
|
||||
|
||||
CONSOLE_EXEC_STATE_FIND_ARG,
|
||||
CONSOLE_EXEC_STATE_PARSE_ARG,
|
||||
CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED,
|
||||
CONSOLE_EXEC_STATE_ARG_PARSED,
|
||||
|
||||
CONSOLE_EXEC_STATE_CMD_FINISHED,
|
||||
CONSOLE_EXEC_STATE_FULLY_PARSED
|
||||
} consoleexecstate_t;
|
||||
|
||||
typedef struct {
|
||||
consolecmd_t commands[CONSOLE_COMMANDS_MAX];
|
||||
uint32_t commandCount;
|
||||
|
||||
consolevar_t variables[CONSOLE_VARIABLES_MAX];
|
||||
uint32_t variableCount;
|
||||
|
||||
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
|
||||
|
||||
consolecmdexec_t execBuffer[CONSOLE_EXEC_BUFFER_MAX];
|
||||
uint32_t execBufferCount;
|
||||
|
||||
consolecmd_t *cmdGet;
|
||||
consolecmd_t *cmdSet;
|
||||
pthread_mutex_t lock; // Mutex for thread safety
|
||||
|
||||
// May move these later
|
||||
char_t inputBuffer[CONSOLE_LINE_MAX];
|
||||
int32_t inputIndex;
|
||||
|
||||
bool_t open;
|
||||
} console_t;
|
||||
|
||||
extern console_t CONSOLE;
|
||||
|
||||
/**
|
||||
* Initializes the console.
|
||||
*/
|
||||
void consoleInit();
|
||||
|
||||
/**
|
||||
* Registers a console command.
|
||||
*
|
||||
* @param name The name of the command.
|
||||
* @param function The function to execute when the command is called.
|
||||
* @return The registered command.
|
||||
*/
|
||||
consolecmd_t * consoleRegCmd(const char_t *name, consolecmdfunc_t function);
|
||||
|
||||
/**
|
||||
* Registers a console variable.
|
||||
*
|
||||
* @param name The name of the variable.
|
||||
* @param value The initial value of the variable.
|
||||
* @param event The event to register.
|
||||
* @return The registered variable.
|
||||
*/
|
||||
consolevar_t * consoleRegVar(
|
||||
const char_t *name,
|
||||
const char_t *value,
|
||||
consolevarchanged_t event
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the value of a console variable.
|
||||
*
|
||||
* @param name The name of the variable.
|
||||
* @param value The new value of the variable.
|
||||
*/
|
||||
void consolePrint(
|
||||
const char_t *message,
|
||||
...
|
||||
);
|
||||
|
||||
/**
|
||||
* Executes a console command.
|
||||
*
|
||||
* @param line The line to execute.
|
||||
*/
|
||||
void consoleExec(const char_t *line);
|
||||
|
||||
/**
|
||||
* Processes the console's pending commands.
|
||||
*/
|
||||
void consoleProcess();
|
||||
|
||||
/**
|
||||
* Updates the console's input buffer and handles user input.
|
||||
*/
|
||||
void consoleUpdate();
|
||||
|
||||
/**
|
||||
* Draws the console's output.
|
||||
*/
|
||||
void consoleDraw();
|
||||
|
||||
void cmdGet(const consolecmdexec_t *exec);
|
||||
void cmdSet(const consolecmdexec_t *exec);
|
||||
void cmdEcho(const consolecmdexec_t *exec);
|
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "consolecmd.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
void consoleCmdInit(
|
||||
consolecmd_t *cmd,
|
||||
const char_t *name,
|
||||
consolecmdfunc_t function
|
||||
) {
|
||||
assertNotNull(cmd, "Command is NULL.");
|
||||
assertNotNull(name, "Name is NULL.");
|
||||
assertNotNull(function, "Function is NULL.");
|
||||
assertStrLenMin(name, 1, "Name is empty.");
|
||||
assertStrLenMax(name, CONSOLE_CMD_NAME_MAX, "Name is too long.");
|
||||
|
||||
memoryZero(cmd, sizeof(consolecmd_t));
|
||||
stringCopy(cmd->name, name, CONSOLE_CMD_NAME_MAX);
|
||||
cmd->function = function;
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "consoledefs.h"
|
||||
|
||||
typedef struct consolecmd_s consolecmd_t;
|
||||
|
||||
typedef struct {
|
||||
consolecmd_t *cmd;
|
||||
char_t command[CONSOLE_LINE_MAX];
|
||||
char_t argv[CONSOLE_CMD_ARGC_MAX][CONSOLE_LINE_MAX];
|
||||
uint32_t argc;
|
||||
} consolecmdexec_t;
|
||||
|
||||
typedef void (*consolecmdfunc_t)(const consolecmdexec_t *exec);
|
||||
|
||||
typedef struct consolecmd_s {
|
||||
char_t name[CONSOLE_CMD_NAME_MAX];
|
||||
consolecmdfunc_t function;
|
||||
} consolecmd_t;
|
||||
|
||||
/**
|
||||
* Initializes a console command.
|
||||
*
|
||||
* @param cmd Pointer to the console command.
|
||||
* @param name The name of the command.
|
||||
* @param function The function to execute when the command is called.
|
||||
*/
|
||||
void consoleCmdInit(
|
||||
consolecmd_t *cmd,
|
||||
const char_t *name,
|
||||
consolecmdfunc_t function
|
||||
);
|
@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define CONSOLE_CMD_NAME_MAX 32
|
||||
#define CONSOLE_CMD_ARGC_MAX 16
|
||||
|
||||
#define CONSOLE_COMMANDS_MAX 128
|
||||
#define CONSOLE_VARIABLES_MAX 128
|
||||
#define CONSOLE_LINE_MAX 256
|
||||
#define CONSOLE_HISTORY_MAX 32
|
||||
#define CONSOLE_EXEC_BUFFER_MAX 16
|
||||
|
||||
#define CONSOLE_VAR_NAME_MAX 32
|
||||
#define CONSOLE_VAR_VALUE_MAX 128
|
||||
#define CONSOLE_VAR_EVENTS_MAX 8
|
@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "consolevar.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
void consoleVarInit(
|
||||
consolevar_t *var,
|
||||
const char_t *name,
|
||||
const char_t *value
|
||||
) {
|
||||
assertNotNull(var, "var must not be NULL");
|
||||
assertNotNull(name, "name must not be NULL");
|
||||
assertNotNull(value, "value must not be NULL");
|
||||
|
||||
assertStrLenMin(name, 1, "name must not be empty");
|
||||
assertStrLenMax(name, CONSOLE_VAR_NAME_MAX, "name is too long");
|
||||
assertStrLenMax(value, CONSOLE_VAR_VALUE_MAX, "value is too long");
|
||||
|
||||
memoryZero(var, sizeof(consolevar_t));
|
||||
stringCopy(var->name, name, CONSOLE_VAR_NAME_MAX);
|
||||
stringCopy(var->value, value, CONSOLE_VAR_VALUE_MAX);
|
||||
}
|
||||
|
||||
void consoleVarInitListener(
|
||||
consolevar_t *var,
|
||||
const char_t *name,
|
||||
const char_t *value,
|
||||
consolevarchanged_t event
|
||||
) {
|
||||
consoleVarInit(var, name, value);
|
||||
if(event) consoleVarListen(var, event);
|
||||
}
|
||||
|
||||
void consoleVarSetValue(consolevar_t *var, const char_t *value) {
|
||||
assertNotNull(var, "var must not be NULL");
|
||||
assertNotNull(value, "value must not be NULL");
|
||||
assertStrLenMax(value, CONSOLE_VAR_VALUE_MAX, "value is too long");
|
||||
|
||||
stringCopy(var->value, value, CONSOLE_VAR_VALUE_MAX);
|
||||
|
||||
uint8_t i = 0;
|
||||
while (i < var->eventCount) {
|
||||
var->events[i](var);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void consoleVarListen(consolevar_t *var, consolevarchanged_t event) {
|
||||
assertNotNull(var, "var must not be NULL");
|
||||
assertNotNull(event, "event must not be NULL");
|
||||
assertTrue(
|
||||
var->eventCount < CONSOLE_VAR_EVENTS_MAX,
|
||||
"Event count is too high"
|
||||
);
|
||||
var->events[var->eventCount++] = event;
|
||||
}
|
||||
|
@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "consoledefs.h"
|
||||
|
||||
typedef struct consolevar_s consolevar_t;
|
||||
|
||||
typedef void (*consolevarchanged_t)(const consolevar_t *var);
|
||||
|
||||
typedef struct consolevar_s {
|
||||
char_t name[CONSOLE_VAR_NAME_MAX];
|
||||
char_t value[CONSOLE_VAR_VALUE_MAX];
|
||||
consolevarchanged_t events[CONSOLE_VAR_EVENTS_MAX];
|
||||
uint8_t eventCount;
|
||||
} consolevar_t;
|
||||
|
||||
/**
|
||||
* Initializes a console variable.
|
||||
*
|
||||
* @param var Pointer to the console variable.
|
||||
* @param name The name of the variable.
|
||||
* @param value The initial value of the variable.
|
||||
*/
|
||||
void consoleVarInit(
|
||||
consolevar_t *var,
|
||||
const char_t *name,
|
||||
const char_t *value
|
||||
);
|
||||
|
||||
/**
|
||||
* Initializes a console variable with a listener.
|
||||
*
|
||||
* @param var Pointer to the console variable.
|
||||
* @param name The name of the variable.
|
||||
* @param value The initial value of the variable.
|
||||
* @param event The event to register.
|
||||
*/
|
||||
void consoleVarInitListener(
|
||||
consolevar_t *var,
|
||||
const char_t *name,
|
||||
const char_t *value,
|
||||
consolevarchanged_t event
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the value of a console variable.
|
||||
*
|
||||
* @param var Pointer to the console variable.
|
||||
* @param value The new value of the variable.
|
||||
*/
|
||||
void consoleVarSetValue(consolevar_t *var, const char_t *value);
|
||||
|
||||
/**
|
||||
* Registers an event to be called when the value of a console variable changes.
|
||||
*
|
||||
* @param var Pointer to the console variable.
|
||||
* @param event The event to register.
|
||||
*/
|
||||
void consoleVarListen(consolevar_t *var, consolevarchanged_t event);
|
@ -7,6 +7,4 @@
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
render.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
)
|
@ -6,31 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "render.h"
|
||||
#include "game/game.h"
|
||||
#include "assert/assert.h"
|
||||
#include "console/console.h"
|
||||
|
||||
void renderInit() {
|
||||
// Initialize the rendering system
|
||||
InitWindow(800, 600, "Dusk");
|
||||
}
|
||||
|
||||
renderresult_t renderDraw() {
|
||||
BeginDrawing();
|
||||
ClearBackground(BLACK);
|
||||
|
||||
for(uint32_t i = 0; i < GAME.entityCount; i++) {
|
||||
entityRender(&GAME.entities[i]);
|
||||
}
|
||||
|
||||
consoleDraw();
|
||||
|
||||
EndDrawing();
|
||||
if(WindowShouldClose()) return RENDER_EXIT;
|
||||
return RENDER_OK;
|
||||
}
|
||||
|
||||
|
||||
void renderDispose() {
|
||||
CloseWindow();
|
||||
|
||||
}
|
@ -6,33 +6,8 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
#define RENDER_FONT_SIZE 20
|
||||
|
||||
typedef enum {
|
||||
RENDER_OK,
|
||||
RENDER_EXIT,
|
||||
RENDER_ERROR
|
||||
} renderresult_t;
|
||||
|
||||
/**
|
||||
* Initializes the rendering system.
|
||||
*
|
||||
* @return The result of the initialization.
|
||||
* Init the render system.
|
||||
*/
|
||||
void renderInit();
|
||||
|
||||
/**
|
||||
* Initializes the rendering system.
|
||||
*
|
||||
* @return The result of the initialization.
|
||||
*/
|
||||
renderresult_t renderDraw();
|
||||
|
||||
/**
|
||||
* Renders the game.
|
||||
*
|
||||
* @return The result of the render operation.
|
||||
*/
|
||||
void renderDispose();
|
||||
void renderInit();
|
@ -16,10 +16,8 @@
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
#include <pthread.h>
|
||||
#include <raylib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
typedef bool bool_t;
|
||||
typedef char char_t;
|
||||
|
@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "error.h"
|
||||
|
||||
errorstack_t ERROR_STACK;
|
||||
|
||||
errorret_t error(const char_t *message, ...) {
|
||||
return errorCode(1, message);
|
||||
}
|
||||
|
||||
errorret_t errorCode(const errorret_t code, const char_t *message, ...) {
|
||||
if(ERROR_STACK.code != ERROR_OK) {
|
||||
snprintf(
|
||||
ERROR_STACK.message,
|
||||
ERROR_STACK_SIZE,
|
||||
"Multiple errors encountered."
|
||||
);
|
||||
errorPrint();
|
||||
return code;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
vsnprintf(ERROR_STACK.message, ERROR_STACK_SIZE, message, args);
|
||||
va_end(args);
|
||||
|
||||
return ERROR_STACK.code = code;
|
||||
}
|
||||
|
||||
bool_t errorCheck() {
|
||||
return ERROR_STACK.code != ERROR_OK;
|
||||
}
|
||||
|
||||
const char_t * errorString() {
|
||||
if(!errorCheck()) return NULL;
|
||||
return ERROR_STACK.message;
|
||||
}
|
||||
|
||||
void errorFlush() {
|
||||
if(!errorCheck()) return;
|
||||
ERROR_STACK.code = ERROR_OK;
|
||||
ERROR_STACK.message[0] = '\0';
|
||||
}
|
||||
|
||||
errorret_t errorPrint() {
|
||||
if(!errorCheck()) return ERROR_OK;
|
||||
printf("Error: %s\n", errorString());
|
||||
errorret_t code = ERROR_STACK.code;
|
||||
errorFlush();
|
||||
return code;
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef int32_t errorret_t;
|
||||
#define ERROR_OK 0
|
||||
|
||||
#define ERROR_STACK_SIZE 256
|
||||
|
||||
typedef struct {
|
||||
char_t message[ERROR_STACK_SIZE + 1];
|
||||
errorret_t code;
|
||||
} errorstack_t;
|
||||
|
||||
extern errorstack_t ERROR_STACK;
|
||||
|
||||
/**
|
||||
* Pushes an error message to the error stack.
|
||||
*
|
||||
* @param message Message to push to the error stack.
|
||||
* @param ... Arguments to format the message with.
|
||||
*/
|
||||
errorret_t error(const char_t *message, ...);
|
||||
|
||||
/**
|
||||
* Pushes an error message to the error stack with a given error code.
|
||||
*
|
||||
* @param code Error code to push to the error stack.
|
||||
* @param message Message to push to the error stack.
|
||||
* @param ... Arguments to format the message with.
|
||||
*/
|
||||
errorret_t errorCode(const errorret_t code, const char_t *message, ...);
|
||||
|
||||
/**
|
||||
* Checks if an error has been pushed to the error stack.
|
||||
*
|
||||
* @return True if an error has been pushed to the error stack.
|
||||
*/
|
||||
bool_t errorCheck();
|
||||
|
||||
/**
|
||||
* Retrieves the error message from the error stack.
|
||||
*/
|
||||
const char_t * errorString();
|
||||
|
||||
/**
|
||||
* Clears the error stack.
|
||||
*/
|
||||
void errorFlush();
|
||||
|
||||
/**
|
||||
* Prints the error stack to the console. This also clears the error stack.
|
||||
*/
|
||||
errorret_t errorPrint();
|
@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
DIRECTION_SOUTH = 0,
|
||||
DIRECTION_NORTH = 1,
|
||||
DIRECTION_EAST = 2,
|
||||
DIRECTION_WEST = 3,
|
||||
|
||||
DIRECTION_DOWN = 0,
|
||||
DIRECTION_UP = 1,
|
||||
DIRECTION_RIGHT = 2,
|
||||
DIRECTION_LEFT = 3,
|
||||
} direction_t;
|
@ -1,78 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity.h"
|
||||
#include "game/game.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "display/render.h"
|
||||
#include "input.h"
|
||||
|
||||
void entityInit(entity_t *entity, const entitytype_t type) {
|
||||
memoryZero(entity, sizeof(entity_t));
|
||||
|
||||
entity->type = type;
|
||||
switch(type) {
|
||||
case ENTITY_TYPE_PLAYER:
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Unknown entity type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void entityTest(entity_t *ent) {
|
||||
if(inputIsPressed(INPUT_DOWN)) {
|
||||
ent->direction = DIRECTION_DOWN;
|
||||
if(ent->y < GAME.map.height - 1) ent->y++;
|
||||
} else if(inputIsPressed(INPUT_UP)) {
|
||||
ent->direction = DIRECTION_UP;
|
||||
if(ent->y > 0) ent->y--;
|
||||
} else if(inputIsPressed(INPUT_LEFT)) {
|
||||
ent->direction = DIRECTION_LEFT;
|
||||
if(ent->x > 0) ent->x--;
|
||||
} else if(inputIsPressed(INPUT_RIGHT)) {
|
||||
ent->direction = DIRECTION_RIGHT;
|
||||
if(ent->x < GAME.map.width - 1) ent->x++;
|
||||
}
|
||||
}
|
||||
|
||||
void entityRender(const entity_t *ent) {
|
||||
// Draw the entity
|
||||
int32_t fontSize = 20;
|
||||
char_t str[1];
|
||||
switch(ent->direction) {
|
||||
case DIRECTION_NORTH:
|
||||
str[0] = '^';
|
||||
break;
|
||||
case DIRECTION_EAST:
|
||||
str[0] = '>';
|
||||
break;
|
||||
case DIRECTION_SOUTH:
|
||||
str[0] = 'v';
|
||||
break;
|
||||
case DIRECTION_WEST:
|
||||
str[0] = '<';
|
||||
break;
|
||||
default:
|
||||
assertUnreachable("Invalid entity direction.");
|
||||
break;
|
||||
}
|
||||
|
||||
DrawTextEx(
|
||||
GetFontDefault(),
|
||||
str,
|
||||
(struct Vector2){
|
||||
ent->x * RENDER_FONT_SIZE,
|
||||
ent->y * RENDER_FONT_SIZE
|
||||
},
|
||||
RENDER_FONT_SIZE,
|
||||
(float)(RENDER_FONT_SIZE / 10),
|
||||
WHITE
|
||||
);
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "game/direction.h"
|
||||
|
||||
typedef enum {
|
||||
ENTITY_TYPE_NULL = 0,
|
||||
ENTITY_TYPE_PLAYER,
|
||||
} entitytype_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
entitytype_t type;
|
||||
uint8_t x, y;
|
||||
direction_t direction;
|
||||
} entity_t;
|
||||
|
||||
/**
|
||||
* Initializes a given entity.
|
||||
*
|
||||
* @param entity The entity to initialize.
|
||||
* @param type The type of the entity.
|
||||
*/
|
||||
void entityInit(entity_t *entity, const entitytype_t type);
|
||||
|
||||
void entityTest(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Renders a given entity.
|
||||
*
|
||||
* @param entity The entity to render.
|
||||
*/
|
||||
void entityRender(const entity_t *entity);
|
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "game.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
game_t GAME;
|
||||
|
||||
void gameInit(void) {
|
||||
memoryZero(&GAME, sizeof(game_t));
|
||||
|
||||
GAME.map.width = 10;
|
||||
GAME.map.height = 10;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "game/entity/entity.h"
|
||||
#include "game/map/map.h"
|
||||
|
||||
#define GAME_ENTITIES_MAX 256
|
||||
|
||||
typedef struct {
|
||||
entity_t entities[GAME_ENTITIES_MAX];
|
||||
uint32_t entityCount;
|
||||
map_t map;
|
||||
} game_t;
|
||||
|
||||
extern game_t GAME;
|
||||
|
||||
/**
|
||||
* Initializes the game.
|
||||
*/
|
||||
void gameInit(void);
|
58
src/input.c
58
src/input.c
@ -9,20 +9,6 @@
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
inputbindmap_t INPUT_BIND_MAPS[] = {
|
||||
{ KEY_W, INPUT_UP },
|
||||
{ KEY_S, INPUT_DOWN },
|
||||
{ KEY_A, INPUT_LEFT },
|
||||
{ KEY_D, INPUT_RIGHT },
|
||||
{ KEY_ENTER, INPUT_ACCEPT },
|
||||
{ KEY_ESCAPE, INPUT_CANCEL },
|
||||
{ KEY_UP, INPUT_UP },
|
||||
{ KEY_DOWN, INPUT_DOWN },
|
||||
{ KEY_LEFT, INPUT_LEFT },
|
||||
{ KEY_RIGHT, INPUT_RIGHT },
|
||||
{ KEY_GRAVE, INPUT_TOGGLE_CONSOLE }
|
||||
};
|
||||
|
||||
#define INPUT_BIND_MAP_COUNT (sizeof(INPUT_BIND_MAPS) / sizeof(inputbindmap_t))
|
||||
|
||||
input_t INPUT;
|
||||
@ -32,48 +18,4 @@ void inputInit(void) {
|
||||
}
|
||||
|
||||
void inputUpdate(void) {
|
||||
memoryCopy(
|
||||
INPUT.previous,
|
||||
INPUT.current,
|
||||
sizeof(INPUT.previous)
|
||||
);
|
||||
|
||||
memoryZero(
|
||||
INPUT.current,
|
||||
sizeof(INPUT.current)
|
||||
);
|
||||
|
||||
for(uint32_t i = 0; i < INPUT_BIND_MAP_COUNT; i++) {
|
||||
const inputbindmap_t *map = &INPUT_BIND_MAPS[i];
|
||||
if(IsKeyDown(map->key)) INPUT.current[map->bind] = true;
|
||||
}
|
||||
|
||||
INPUT.keyPressed = GetKeyPressed();
|
||||
INPUT.charPressed = GetCharPressed();
|
||||
}
|
||||
|
||||
bool_t inputIsDown(const inputbind_t input) {
|
||||
assertTrue(input < INPUT_BIND_COUNT, "Input out of range");
|
||||
return INPUT.current[input];
|
||||
}
|
||||
|
||||
bool_t inputIsUp(const inputbind_t input) {
|
||||
return !inputIsDown(input);
|
||||
}
|
||||
|
||||
bool_t inputWasDown(const inputbind_t input) {
|
||||
assertTrue(input < INPUT_BIND_COUNT, "Input out of range");
|
||||
return INPUT.previous[input];
|
||||
}
|
||||
|
||||
bool_t inputWasUp(const inputbind_t input) {
|
||||
return !inputWasDown(input);
|
||||
}
|
||||
|
||||
bool_t inputIsPressed(const inputbind_t input) {
|
||||
return inputIsDown(input) && inputWasUp(input);
|
||||
}
|
||||
|
||||
bool_t inputIsReleased(const inputbind_t input) {
|
||||
return inputIsUp(input) && inputWasDown(input);
|
||||
}
|
72
src/input.h
72
src/input.h
@ -8,28 +8,8 @@
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
INPUT_UP,
|
||||
INPUT_DOWN,
|
||||
INPUT_LEFT,
|
||||
INPUT_RIGHT,
|
||||
INPUT_ACCEPT,
|
||||
INPUT_CANCEL,
|
||||
INPUT_TOGGLE_CONSOLE,
|
||||
} inputbind_t;
|
||||
|
||||
#define INPUT_BIND_COUNT INPUT_TOGGLE_CONSOLE + 1
|
||||
|
||||
typedef struct {
|
||||
const int32_t key;
|
||||
const inputbind_t bind;
|
||||
} inputbindmap_t;
|
||||
|
||||
typedef struct {
|
||||
bool_t current[INPUT_BIND_COUNT];
|
||||
bool_t previous[INPUT_BIND_COUNT];
|
||||
int32_t keyPressed;
|
||||
char_t charPressed;
|
||||
char_t input;
|
||||
} input_t;
|
||||
|
||||
extern input_t INPUT;
|
||||
@ -42,52 +22,4 @@ void inputInit(void);
|
||||
/**
|
||||
* Updates the input system.
|
||||
*/
|
||||
void inputUpdate(void);
|
||||
|
||||
/**
|
||||
* Returns whether a given input is currently pressed.
|
||||
*
|
||||
* @param input The input to check.
|
||||
* @return True if the input is pressed, false otherwise.
|
||||
*/
|
||||
bool_t inputIsDown(const inputbind_t input);
|
||||
|
||||
/**
|
||||
* Returns whether a given input is currently up.
|
||||
*
|
||||
* @param input The input to check.
|
||||
* @return True if the input is up, false otherwise.
|
||||
*/
|
||||
bool_t inputIsUp(const inputbind_t input);
|
||||
|
||||
/**
|
||||
* Returns whether a given input was just pressed.
|
||||
*
|
||||
* @param input The input to check.
|
||||
* @return True if the input was just pressed, false otherwise.
|
||||
*/
|
||||
bool_t inputWasDown(const inputbind_t input);
|
||||
|
||||
/**
|
||||
* Returns whether a given input was just released.
|
||||
*
|
||||
* @param input The input to check.
|
||||
* @return True if the input was just released, false otherwise.
|
||||
*/
|
||||
bool_t inputWasUp(const inputbind_t input);
|
||||
|
||||
/**
|
||||
* Returns whether a given input was just pressed.
|
||||
*
|
||||
* @param input The input to check.
|
||||
* @return True if the input was just pressed, false otherwise.
|
||||
*/
|
||||
bool_t inputIsPressed(const inputbind_t input);
|
||||
|
||||
/**
|
||||
* Returns whether a given input was just released.
|
||||
*
|
||||
* @param input The input to check.
|
||||
* @return True if the input was just released, false otherwise.
|
||||
*/
|
||||
bool_t inputIsReleased(const inputbind_t input);
|
||||
void inputUpdate(void);
|
59
src/main.c
59
src/main.c
@ -5,70 +5,17 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assert/assert.h"
|
||||
#include "game/game.h"
|
||||
#include "display/render.h"
|
||||
#include "input.h"
|
||||
#include "console/console.h"
|
||||
#include "network/client/client.h"
|
||||
#include "network/server/server.h"
|
||||
#include "util/random.h"
|
||||
|
||||
bool_t exitRequested = false;
|
||||
|
||||
void cmdExit(const consolecmdexec_t *exec) {
|
||||
exitRequested = true;
|
||||
}
|
||||
#include "display/render.h"
|
||||
|
||||
int32_t main(const int32_t argc, const char **argv) {
|
||||
assertInit();
|
||||
randomInit();
|
||||
gameInit();
|
||||
inputInit();
|
||||
renderInit();
|
||||
consoleInit();
|
||||
clientInit();
|
||||
serverInit();
|
||||
|
||||
consoleRegCmd("exit", cmdExit);
|
||||
|
||||
entityInit(&GAME.entities[GAME.entityCount++], ENTITY_TYPE_PLAYER);
|
||||
|
||||
float_t lastPing = -1;
|
||||
float_t time = 0;
|
||||
|
||||
while(true) {
|
||||
inputUpdate();
|
||||
consoleUpdate();
|
||||
serverUpdate();
|
||||
clientUpdate();
|
||||
|
||||
if(!CONSOLE.open) {
|
||||
for(uint32_t i = 0; i < GAME.entityCount; i++) {
|
||||
entity_t *ent = &GAME.entities[i];
|
||||
entityTest(ent);
|
||||
}
|
||||
}
|
||||
while(1) {
|
||||
|
||||
renderresult_t result = renderDraw();
|
||||
if(result != RENDER_OK) break;
|
||||
if(exitRequested) break;
|
||||
|
||||
time += GetFrameTime();
|
||||
if(time - lastPing > 1.0f) {
|
||||
lastPing = time;
|
||||
|
||||
if(CLIENT.state != CLIENT_STATE_CONNECTED) continue;
|
||||
packet_t packet;
|
||||
packetPingCreate(&packet);
|
||||
packetQueuePushOut(&CLIENT.packetQueue, &packet);
|
||||
lastPing = time;
|
||||
}
|
||||
usleep(16 * 1000); // Sleep for 16 milliseconds (60 FPS)
|
||||
}
|
||||
|
||||
serverDispose();
|
||||
clientDispose();
|
||||
renderDispose();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(client)
|
||||
add_subdirectory(packet)
|
||||
add_subdirectory(server)
|
@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
client.c
|
||||
)
|
||||
|
||||
# Subdirs
|
@ -1,96 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "client.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
client_t CLIENT;
|
||||
|
||||
void clientInit() {
|
||||
memoryZero(&CLIENT, sizeof(client_t));
|
||||
}
|
||||
|
||||
void clientConnect(const clientconnect_t connect) {
|
||||
int32_t error;
|
||||
|
||||
assertTrue(
|
||||
CLIENT.state == CLIENT_STATE_DISCONNECTED,
|
||||
"Client is not in a disconnected state."
|
||||
);
|
||||
|
||||
// Reset state
|
||||
packetQueueInit(&CLIENT.queue);
|
||||
CLIENT.error = CLIENT_ERROR_NO_ERROR;
|
||||
CLIENT.state = CLIENT_STATE_CONNECTING;
|
||||
CLIENT.type = connect.type;
|
||||
|
||||
// Init the properties based on the type of client.
|
||||
switch(connect.type) {
|
||||
case CLIENT_TYPE_NETWORKED:
|
||||
networkClientConnect(&CLIENT, connect);
|
||||
break;
|
||||
|
||||
case CLIENT_TYPE_SINGLE_PLAYER:
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid client connection type.");
|
||||
break;
|
||||
}
|
||||
|
||||
// Spawn the connect thread which will be responsible from here on out.
|
||||
error = pthread_create(
|
||||
&CLIENT.connectThread,
|
||||
NULL,
|
||||
(void *(*)(void *))clientThreadConnect,
|
||||
&CLIENT
|
||||
);
|
||||
if(error != 0) {
|
||||
CLIENT.state = CLIENT_STATE_DISCONNECTED;
|
||||
CLIENT.error = CLIENT_ERROR_CONNECT_THREAD_SPAWN_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void clientDisconnect() {
|
||||
if(CLIENT.state == CLIENT_STATE_DISCONNECTED) return;
|
||||
if(CLIENT.state == CLIENT_STATE_DISCONNECTING) return;
|
||||
}
|
||||
|
||||
void clientUpdate() {
|
||||
assertIsMainThread("Client update called from non-main thread.");
|
||||
}
|
||||
|
||||
void clientDispose() {
|
||||
if(CLIENT.state != CLIENT_STATE_DISCONNECTED) {
|
||||
clientDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void * clientThreadConnect(void *arg) {
|
||||
assertNotNull(arg, "Client thread connect argument is null.");
|
||||
assertNotMainThread("Client thread connect called from main thread.");
|
||||
|
||||
client_t *client = (client_t *)arg;
|
||||
assertTrue(client == &CLIENT, "Invalid client thread arg.");
|
||||
|
||||
if(client->state) return NULL;
|
||||
|
||||
switch(client->type) {
|
||||
case CLIENT_TYPE_NETWORKED:
|
||||
break;
|
||||
|
||||
case CLIENT_TYPE_SINGLE_PLAYER:
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid client type.");
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "network/packet/packetqueue.h"
|
||||
#include "networkclient.h"
|
||||
|
||||
/**
|
||||
* Type of connection used for a client.
|
||||
*/
|
||||
typedef enum {
|
||||
CLIENT_TYPE_SINGLE_PLAYER = 0,
|
||||
CLIENT_TYPE_NETWORKED = 1,
|
||||
} clienttype_t;
|
||||
|
||||
/**
|
||||
* The state of the client and what it is doing.
|
||||
*/
|
||||
typedef enum {
|
||||
CLIENT_STATE_DISCONNECTED = 0,
|
||||
CLIENT_STATE_CONNECTING,
|
||||
CLIENT_STATE_CONNECTED,
|
||||
CLIENT_STATE_DISCONNECTING,
|
||||
} clientstate_t;
|
||||
|
||||
/**
|
||||
* Information used to connect to a server.
|
||||
*/
|
||||
typedef struct clientconnect_s {
|
||||
clienttype_t type;
|
||||
} clientconnect_t;
|
||||
|
||||
/**
|
||||
* Error codes for the client.
|
||||
*/
|
||||
typedef enum {
|
||||
CLIENT_ERROR_NO_ERROR = 0,
|
||||
CLIENT_ERROR_CONNECT_THREAD_SPAWN_FAILED,
|
||||
} clienterror_t;
|
||||
|
||||
/**
|
||||
* The clients state and information.
|
||||
*/
|
||||
typedef struct client_s {
|
||||
clientstate_t state;
|
||||
clienttype_t type;
|
||||
packetqueue_t queue;
|
||||
clienterror_t error;
|
||||
|
||||
pthread_t connectThread;
|
||||
|
||||
union {
|
||||
networkclient_t network;
|
||||
};
|
||||
} client_t;
|
||||
|
||||
extern client_t CLIENT;
|
||||
|
||||
/**
|
||||
* Initializes the client object, does not perform any connection.
|
||||
*/
|
||||
void clientInit();
|
||||
|
||||
/**
|
||||
* Initiates a client connection, this function will not block and will return
|
||||
* immediately even if a connection issue occurs. It is up to the caller to
|
||||
* handle the connection state and any errors that occur.
|
||||
*
|
||||
* Client must be already disconnected before calling this function.
|
||||
*
|
||||
* @param connect The connection information to use.
|
||||
*/
|
||||
void clientConnect(const clientconnect_t connect);
|
||||
|
||||
/**
|
||||
* Requests the client to disconnect from the server. This will not block and
|
||||
* will return immediately. The client will handle the disconnection in the
|
||||
* background.
|
||||
*/
|
||||
void clientDisconnect();
|
||||
|
||||
/**
|
||||
* Internal method that writes a packet to the client immediately.
|
||||
*/
|
||||
clienterror_t clientWritePacket(const packet_t *packet);
|
||||
|
||||
/**
|
||||
* Internal method that reads a packet from the client immediately.
|
||||
*
|
||||
* @param packet The packet to read into.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
clienterror_t clientReadPacket(const packet_t *packet);
|
||||
|
||||
/**
|
||||
* Main update loop for the client, this will handle all incoming packets and
|
||||
* update the client state.
|
||||
*/
|
||||
void clientUpdate();
|
||||
|
||||
/**
|
||||
* Disposes of the client, cleans up any resources and will force disconnect if
|
||||
* not already disconnected.
|
||||
*/
|
||||
void clientDispose();
|
||||
|
||||
/**
|
||||
* Thread function responsible for initiating and handshaking the connection.
|
||||
*
|
||||
* @param arg The client connection information.
|
||||
* @return Returns NULL, unless an error occurs.
|
||||
*/
|
||||
void * clientThreadConnect(void *arg);
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct client_s client_t;
|
||||
typedef struct clientconnect_s clientconnect_t;
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
} networkclient_t;
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
*
|
||||
* @param client The client to initialize.
|
||||
* @param connect The connection information.
|
||||
*/
|
||||
void networkClientConnect(client_t *client, const clientconnect_t connect);
|
@ -1,14 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
packet.c
|
||||
packetwelcome.c
|
||||
packetdisconnect.c
|
||||
packetqueue.c
|
||||
packetping.c
|
||||
)
|
@ -1,84 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "packet.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "network/client/client.h"
|
||||
#include "network/server/server.h"
|
||||
|
||||
packethandler_t PACKET_HANDLERS[] = {
|
||||
{ NULL, NULL },
|
||||
{ packetWelcomeClientProcess, NULL },
|
||||
{ packetDisconnectClientProcess, NULL },
|
||||
{ packetPingClientProcess, packetPingServerProcess },
|
||||
};
|
||||
|
||||
void packetInit(
|
||||
packet_t *packet,
|
||||
const packettype_t type,
|
||||
const uint32_t length
|
||||
) {
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
|
||||
memoryZero(packet, sizeof(packet_t));
|
||||
|
||||
assertTrue(length > 0, "Packet length is 0");
|
||||
assertTrue(
|
||||
length <= sizeof(packetdata_t),
|
||||
"Packet length is too large"
|
||||
);
|
||||
|
||||
packet->type = type;
|
||||
packet->length = length;
|
||||
}
|
||||
|
||||
errorret_t packetClientProcess(
|
||||
const packet_t *packet,
|
||||
client_t *client
|
||||
) {
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertTrue(
|
||||
client->type == CLIENT_TYPE_NETWORKED,
|
||||
"Client is not networked"
|
||||
);
|
||||
assertIsMainThread("Client process must be on main thread");
|
||||
|
||||
if(packet->type >= PACKET_TYPE_COUNT) {
|
||||
return error("Unknown packet type %d", packet->type);
|
||||
}
|
||||
|
||||
if(PACKET_HANDLERS[packet->type].client == NULL) {
|
||||
return error("Packet type %d has no client handler", packet->type);
|
||||
}
|
||||
|
||||
return PACKET_HANDLERS[packet->type].client(packet, client);
|
||||
}
|
||||
|
||||
errorret_t packetServerProcess(
|
||||
const packet_t *packet,
|
||||
serverclient_t *client
|
||||
) {
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
assertIsMainThread("Server client process must be on main thread");
|
||||
|
||||
if(packet->type >= PACKET_TYPE_COUNT) {
|
||||
return error("Unknown packet type %d", packet->type);
|
||||
}
|
||||
|
||||
if(PACKET_HANDLERS[packet->type].server == NULL) {
|
||||
return error("Packet type %d has no server handler", packet->type);
|
||||
}
|
||||
|
||||
return PACKET_HANDLERS[packet->type].server(packet, client);
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "packetwelcome.h"
|
||||
#include "packetdisconnect.h"
|
||||
#include "packetping.h"
|
||||
|
||||
typedef enum {
|
||||
PACKET_TYPE_INVALID = 0,
|
||||
PACKET_TYPE_WELCOME = 1,
|
||||
PACKET_TYPE_DISCONNECT = 2,
|
||||
PACKET_TYPE_PING = 3,
|
||||
} packettype_t;
|
||||
|
||||
#define PACKET_TYPE_COUNT 4
|
||||
|
||||
typedef union {
|
||||
packetwelcome_t welcome;
|
||||
packetdisconnect_t disconnect;
|
||||
packetping_t ping;
|
||||
} packetdata_t;
|
||||
|
||||
typedef struct packet_s {
|
||||
packettype_t type;
|
||||
uint32_t length;
|
||||
packetdata_t data;
|
||||
} packet_t;
|
||||
|
||||
typedef struct {
|
||||
errorret_t (*client)(const packet_t *packet, client_t *client);
|
||||
errorret_t (*server)(const packet_t *packet, serverclient_t *client);
|
||||
} packethandler_t;
|
||||
|
||||
extern packethandler_t PACKET_HANDLERS[];
|
||||
|
||||
/**
|
||||
* Initializes a packet with the given type. This is only to be used by sub
|
||||
* initializers, such as packetWelcomeCreate for example.
|
||||
*
|
||||
* @param packet Pointer to the packet structure to initialize.
|
||||
* @param type The type of the packet.
|
||||
* @param length The length of the packet data.
|
||||
*/
|
||||
void packetInit(
|
||||
packet_t *packet,
|
||||
const packettype_t type,
|
||||
const uint32_t length
|
||||
);
|
||||
|
||||
/**
|
||||
* Processes a packet for a given client. Will auto-decide the correct method to
|
||||
* handle the process
|
||||
*
|
||||
* @param packet Pointer to the packet structure to process.
|
||||
* @param client Pointer to the client structure.
|
||||
* @return ERROR_OK on success, or an error code on failure.
|
||||
*/
|
||||
errorret_t packetClientProcess(
|
||||
const packet_t *packet,
|
||||
client_t *client
|
||||
);
|
||||
|
||||
/**
|
||||
* Processes a packet for a given server client. Will auto-decide the correct
|
||||
* method to handle the process
|
||||
*
|
||||
* @param packet Pointer to the packet structure to process.
|
||||
* @param client Pointer to the server client structure.
|
||||
* @return ERROR_OK on success, or an error code on failure.
|
||||
*/
|
||||
errorret_t packetServerProcess(
|
||||
const packet_t *packet,
|
||||
serverclient_t *client
|
||||
);
|
@ -1,52 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "packet.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "network/client/client.h"
|
||||
|
||||
void packetDisconnectCreate(
|
||||
packet_t *packet,
|
||||
const packetdisconnectreason_t reason
|
||||
) {
|
||||
packetInit(packet, PACKET_TYPE_DISCONNECT, sizeof(packetdisconnect_t));
|
||||
packet->data.disconnect.reason = reason;
|
||||
}
|
||||
|
||||
errorret_t packetDisconnectClientProcess(
|
||||
const packet_t *packet,
|
||||
client_t *client
|
||||
) {
|
||||
assertTrue(
|
||||
packet->type == PACKET_TYPE_DISCONNECT,
|
||||
"Packet type is not DISCONNECT"
|
||||
);
|
||||
|
||||
if(packet->length != sizeof(packetdisconnect_t)) {
|
||||
return error("Disconnect packet length is not correct");
|
||||
}
|
||||
|
||||
// TODO: Handle disconnect reasons better.
|
||||
packetdisconnect_t *data = (packetdisconnect_t *)&packet->data;
|
||||
// switch(data->reason) {
|
||||
// case PACKET_DISCONNECT_REASON_UNKNOWN:
|
||||
// return error("Server disconnected: Unknown reason");
|
||||
// case PACKET_DISCONNECT_REASON_INVALID_VERSION:
|
||||
// return error("Server disconnected: Invalid version");
|
||||
// case PACKET_DISCONNECT_REASON_MALFORMED_PACKET:
|
||||
// return error("Server disconnected: Malformed packet");
|
||||
// case PACKET_DISCONNECT_REASON_SERVER_FULL:
|
||||
// return error("Server disconnected: Server full");
|
||||
// case PACKET_DISCONNECT_REASON_SERVER_SHUTDOWN:
|
||||
// return error("Server disconnected: Server shutdown");
|
||||
// default:
|
||||
// return error("Server disconnected: Unknown reason");
|
||||
// }
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "packetbase.h"
|
||||
|
||||
typedef enum {
|
||||
PACKET_DISCONNECT_REASON_UNKNOWN = 0,
|
||||
PACKET_DISCONNECT_REASON_INVALID_VERSION = 1,
|
||||
PACKET_DISCONNECT_REASON_MALFORMED_PACKET = 2,
|
||||
PACKET_DISCONNECT_REASON_SERVER_FULL = 3,
|
||||
PACKET_DISCONNECT_REASON_SERVER_SHUTDOWN = 4,
|
||||
} packetdisconnectreason_t;
|
||||
|
||||
typedef struct {
|
||||
packetdisconnectreason_t reason;
|
||||
} packetdisconnect_t;
|
||||
|
||||
/**
|
||||
* Creates a disconnect packet.
|
||||
*
|
||||
* @param packet Pointer to the packet structure to initialize.
|
||||
* @param reason The reason for the disconnect.
|
||||
*/
|
||||
void packetDisconnectCreate(
|
||||
packet_t *packet,
|
||||
const packetdisconnectreason_t reason
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles disconnect packet client side.
|
||||
*
|
||||
* @param packet Pointer to the packet structure to handle.
|
||||
* @param client Pointer to the client structure.
|
||||
* @return ERROR_OK on success, or an error code on failure.
|
||||
*/
|
||||
errorret_t packetDisconnectClientProcess(
|
||||
const packet_t *packet, client_t *client
|
||||
);
|
@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "packet.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "network/server/server.h"
|
||||
|
||||
void packetPingCreate(packet_t *packet) {
|
||||
packetInit(packet, PACKET_TYPE_PING, sizeof(packetping_t));
|
||||
packet->data.ping.number = GetRandomValue(0, INT32_MAX);
|
||||
}
|
||||
|
||||
errorret_t packetPingClientProcess(
|
||||
const packet_t *packet,
|
||||
client_t *client
|
||||
) {
|
||||
assertTrue(
|
||||
packet->type == PACKET_TYPE_PING,
|
||||
"Packet type is not PING"
|
||||
);
|
||||
|
||||
printf("Client got Pong!\n");
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
errorret_t packetPingServerProcess(
|
||||
const packet_t *packet,
|
||||
serverclient_t *client
|
||||
) {
|
||||
assertTrue(
|
||||
packet->type == PACKET_TYPE_PING,
|
||||
"Packet type is not PING"
|
||||
);
|
||||
|
||||
if(packet->length != sizeof(packetping_t)) {
|
||||
return error("Ping packet length is not %d", sizeof(packetping_t));
|
||||
}
|
||||
|
||||
printf("Server got Ping!\n");
|
||||
|
||||
// Send ping back to the client.
|
||||
packet_t pong;
|
||||
packetPingCreate(&pong);
|
||||
packetQueuePushOut(&client->packetQueue, &pong);
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "packetbase.h"
|
||||
|
||||
typedef struct {
|
||||
int32_t number;
|
||||
} packetping_t;
|
||||
|
||||
/**
|
||||
* Creates a ping packet.
|
||||
*
|
||||
* @param packet Pointer to the packet structure to initialize.
|
||||
*/
|
||||
void packetPingCreate(packet_t *packet);
|
||||
|
||||
/**
|
||||
* Validates a ping packet received FROM a client INTO a server.
|
||||
*
|
||||
* @param packet Pointer to the packet structure to validate.
|
||||
* @param client Pointer to the server client structure.
|
||||
* @return ERROR_OK on success, or an error code on failure.
|
||||
*/
|
||||
errorret_t packetPingClientProcess(
|
||||
const packet_t *packet,
|
||||
client_t *client
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles a ping packet received FROM a client INTO a server.
|
||||
*
|
||||
* @param packet Pointer to the packet structure to handle.
|
||||
* @param client Pointer to the server client structure.
|
||||
* @return ERROR_OK on success, or an error code on failure.
|
||||
*/
|
||||
errorret_t packetPingServerProcess(
|
||||
const packet_t *packet,
|
||||
serverclient_t *client
|
||||
);
|
@ -1,86 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "packetqueue.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
void packetQueueInit(packetqueue_t *queue) {
|
||||
assertNotNull(queue, "Packet queue is NULL");
|
||||
memoryZero(queue, sizeof(packetqueue_t));
|
||||
pthread_mutex_init(&queue->lock, NULL);
|
||||
}
|
||||
|
||||
void packetQueuePushIn(packetqueue_t *queue, const packet_t *packet) {
|
||||
assertNotNull(queue, "Packet queue is NULL");
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
pthread_mutex_lock(&queue->lock);
|
||||
assertTrue(
|
||||
queue->packetsInCount < PACKET_QUEUE_MAX_SIZE, "Inbound packet queue is full"
|
||||
);
|
||||
|
||||
queue->packetsIn[queue->packetsInCount++] = *packet;
|
||||
pthread_mutex_unlock(&queue->lock);
|
||||
}
|
||||
|
||||
void packetQueuePushOut(packetqueue_t *queue, const packet_t *packet) {
|
||||
assertNotNull(queue, "Packet queue is NULL");
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
pthread_mutex_lock(&queue->lock);
|
||||
assertTrue(
|
||||
queue->packetsOutCount < PACKET_QUEUE_MAX_SIZE, "Outbound packet queue is full"
|
||||
);
|
||||
|
||||
queue->packetsOut[queue->packetsOutCount++] = *packet;
|
||||
pthread_mutex_unlock(&queue->lock);
|
||||
}
|
||||
|
||||
int32_t packetQueuePopIn(packetqueue_t *queue, packet_t *packet) {
|
||||
assertNotNull(queue, "Packet queue is NULL");
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
|
||||
pthread_mutex_lock(&queue->lock);
|
||||
if(queue->packetsInCount == 0) {
|
||||
pthread_mutex_unlock(&queue->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*packet = queue->packetsIn[0];
|
||||
if(queue->packetsInCount > 1) {
|
||||
memoryCopy(
|
||||
&queue->packetsIn[0],
|
||||
&queue->packetsIn[1],
|
||||
(queue->packetsInCount - 1) * sizeof(packet_t)
|
||||
);
|
||||
}
|
||||
queue->packetsInCount--;
|
||||
pthread_mutex_unlock(&queue->lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t packetQueuePopOut(packetqueue_t *queue, packet_t *packet) {
|
||||
assertNotNull(queue, "Packet queue is NULL");
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
|
||||
pthread_mutex_lock(&queue->lock);
|
||||
if(queue->packetsOutCount == 0) {
|
||||
pthread_mutex_unlock(&queue->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*packet = queue->packetsOut[0];
|
||||
if(queue->packetsOutCount > 1) {
|
||||
memoryCopy(
|
||||
&queue->packetsOut[0],
|
||||
&queue->packetsOut[1],
|
||||
(queue->packetsOutCount - 1) * sizeof(packet_t)
|
||||
);
|
||||
}
|
||||
queue->packetsOutCount--;
|
||||
pthread_mutex_unlock(&queue->lock);
|
||||
return 1;
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "network/packet/packet.h"
|
||||
|
||||
#define PACKET_QUEUE_MAX_SIZE 512
|
||||
|
||||
typedef struct {
|
||||
packet_t packetsIn[PACKET_QUEUE_MAX_SIZE];
|
||||
uint32_t packetsInCount;
|
||||
packet_t packetsOut[PACKET_QUEUE_MAX_SIZE];
|
||||
uint32_t packetsOutCount;
|
||||
pthread_mutex_t lock;
|
||||
} packetqueue_t;
|
||||
|
||||
/**
|
||||
* Initializes the packet queue.
|
||||
*
|
||||
* @param queue Pointer to the packet queue structure.
|
||||
*/
|
||||
void packetQueueInit(packetqueue_t *queue);
|
||||
|
||||
/**
|
||||
* Pushes a packet into the inbound packet queue.
|
||||
*
|
||||
* @param queue Pointer to the packet queue structure.
|
||||
* @param packet Pointer to the packet to be pushed.
|
||||
*/
|
||||
void packetQueuePushIn(packetqueue_t *queue, const packet_t *packet);
|
||||
|
||||
/**
|
||||
* Pushes a packet into the outbound packet queue.
|
||||
*
|
||||
* @param queue Pointer to the packet queue structure.
|
||||
* @param packet Pointer to the packet to be pushed.
|
||||
*/
|
||||
void packetQueuePushOut(packetqueue_t *queue, const packet_t *packet);
|
||||
|
||||
/**
|
||||
* Pops a packet from the inbound packet queue.
|
||||
*
|
||||
* @param queue Pointer to the packet queue structure.
|
||||
* @param packet Pointer to the packet to store the popped packet.
|
||||
* @return 1 if a packet was popped, 0 otherwise.
|
||||
*/
|
||||
int32_t packetQueuePopIn(packetqueue_t *queue, packet_t *packet);
|
||||
|
||||
/**
|
||||
* Pops a packet from the outbound packet queue.
|
||||
*
|
||||
* @param queue Pointer to the packet queue structure.
|
||||
* @param packet Pointer to the packet to store the popped packet.
|
||||
* @return 1 if a packet was popped, 0 otherwise.
|
||||
*/
|
||||
int32_t packetQueuePopOut(packetqueue_t *queue, packet_t *packet);
|
@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "packet.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void packetWelcomeCreate(packet_t *packet) {
|
||||
packetInit(packet, PACKET_TYPE_WELCOME, PACKET_WELCOME_SIZE);
|
||||
memoryCopy(
|
||||
packet->data.welcome.dusk, PACKET_WELCOME_STRING, PACKET_WELCOME_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
errorret_t packetWelcomeClientProcess(
|
||||
const packet_t *packet,
|
||||
client_t *client
|
||||
) {
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertTrue(packet->type == PACKET_TYPE_WELCOME, "Packet type is not WELCOME");
|
||||
|
||||
if(packet->length != PACKET_WELCOME_SIZE) {
|
||||
return error("Welcome packet length is not %d", PACKET_WELCOME_SIZE);
|
||||
}
|
||||
|
||||
if(
|
||||
memoryCompare(
|
||||
packet->data.welcome.dusk, PACKET_WELCOME_STRING, PACKET_WELCOME_SIZE
|
||||
) != 0
|
||||
) {
|
||||
return error("Welcome packet data is not %s", PACKET_WELCOME_STRING);
|
||||
}
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "packetbase.h"
|
||||
|
||||
#define PACKET_WELCOME_STRING "DUSK"
|
||||
#define PACKET_WELCOME_SIZE 4
|
||||
|
||||
typedef struct {
|
||||
char_t dusk[PACKET_WELCOME_SIZE];
|
||||
} packetwelcome_t;
|
||||
|
||||
/**
|
||||
* Creates a welcome packet.
|
||||
*
|
||||
* @param packet Pointer to the packet structure to initialize.
|
||||
*/
|
||||
void packetWelcomeCreate(packet_t *packet);
|
||||
|
||||
/**
|
||||
* Handles a welcome packet received FROM a server INTO a client.
|
||||
*
|
||||
* @param packet Pointer to the packet structure to handle.
|
||||
* @param client Pointer to the client structure.
|
||||
* @return ERROR_OK on success, or an error code on failure.
|
||||
*/
|
||||
errorret_t packetWelcomeClientProcess(
|
||||
const packet_t *packet,
|
||||
client_t *client
|
||||
);
|
@ -1,15 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
server.c
|
||||
serverclient.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(networked)
|
||||
add_subdirectory(singleplayer)
|
@ -1,11 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
networkedserverclient.c
|
||||
networkedserver.c
|
||||
)
|
@ -1,224 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "network/server/server.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "console/console.h"
|
||||
|
||||
errorret_t networkedServerStart(
|
||||
server_t *server,
|
||||
const serverstart_t start
|
||||
) {
|
||||
assertNotNull(server, "Server is null");
|
||||
assertTrue(start.type == SERVER_TYPE_NETWORKED, "Invalid server type");
|
||||
assertTrue(server->state == SERVER_STATE_STOPPED,"Server is already running");
|
||||
assertIsMainThread("Server start must be on main thread");
|
||||
|
||||
// Initialize the networked server.
|
||||
consolePrint("Starting server on port %d...", start.networked.port);
|
||||
memoryZero(&server->networked, sizeof(server->networked));
|
||||
|
||||
// Initialize the server socket
|
||||
server->networked.socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if(server->networked.socket < 0) {
|
||||
return error("Failed to create socket");
|
||||
}
|
||||
|
||||
// Setup the socket options
|
||||
server->networked.address.sin_addr.s_addr = INADDR_ANY;
|
||||
server->networked.address.sin_family = AF_INET;
|
||||
server->networked.address.sin_port = htons(start.networked.port);
|
||||
|
||||
// Set the socket to reuse the address
|
||||
int opt = 1;
|
||||
setsockopt(
|
||||
server->networked.socket,
|
||||
SOL_SOCKET,
|
||||
SO_REUSEADDR,
|
||||
&opt,
|
||||
sizeof(opt)
|
||||
);
|
||||
|
||||
// Set the socket to non-blocking
|
||||
int flags = fcntl(server->networked.socket, F_GETFL, 0);
|
||||
if(
|
||||
flags < 0 ||
|
||||
fcntl(server->networked.socket, F_SETFL, flags | O_NONBLOCK) < 0
|
||||
) {
|
||||
close(server->networked.socket);
|
||||
return error("Failed to set socket to non-blocking %s", strerror(errno));
|
||||
}
|
||||
|
||||
// Bind the socket to the address and port
|
||||
int32_t res = bind(
|
||||
server->networked.socket,
|
||||
(struct sockaddr *)&server->networked.address,
|
||||
sizeof(server->networked.address)
|
||||
);
|
||||
if(res != 0) {
|
||||
close(server->networked.socket);
|
||||
return error(
|
||||
"Socket bind to port %d failed: %s",
|
||||
start.networked.port,
|
||||
strerror(errno)
|
||||
);
|
||||
}
|
||||
|
||||
// Set the socket to listen for incoming connections
|
||||
res = listen(server->networked.socket, SERVER_MAX_CLIENTS);
|
||||
if(res != 0) {
|
||||
close(server->networked.socket);
|
||||
return error(
|
||||
"Failed to listen on port %d: %s",
|
||||
start.networked.port,
|
||||
strerror(errno)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Start the server thread.
|
||||
pthread_mutex_init(&server->networked.lock, NULL);
|
||||
server->state = SERVER_STATE_STARTING;
|
||||
|
||||
res = pthread_create(&server->thread, NULL, networkedServerThread, server);
|
||||
if(res != 0) {
|
||||
close(server->networked.socket);
|
||||
pthread_mutex_destroy(&server->networked.lock);
|
||||
server->state = SERVER_STATE_STOPPED;
|
||||
return error("Failed to create server thread");
|
||||
}
|
||||
|
||||
// Server started, hand back.
|
||||
consolePrint("Server started.");
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
void networkedServerStop(server_t *server) {
|
||||
assertNotNull(server, "Server is null");
|
||||
assertTrue(server->state != SERVER_STATE_STOPPED,"Server is already stopped");
|
||||
assertTrue(server->state != SERVER_STATE_STARTING, "Server is starting");
|
||||
assertIsMainThread("Server stop must be on main thread");
|
||||
|
||||
// Notify thread to stop
|
||||
pthread_mutex_lock(&server->networked.lock);
|
||||
server->state = SERVER_STATE_STOPPING;
|
||||
pthread_mutex_unlock(&server->networked.lock);
|
||||
|
||||
// Disconnect clients
|
||||
uint8_t i = 0;
|
||||
do {
|
||||
serverclient_t *client = &server->clients[i++];
|
||||
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) continue;
|
||||
serverClientClose(client);
|
||||
} while(i < SERVER_MAX_CLIENTS);
|
||||
|
||||
// Wait for the server thread to finish
|
||||
pthread_join(server->thread, NULL);
|
||||
pthread_mutex_destroy(&server->networked.lock);
|
||||
|
||||
close(server->networked.socket);
|
||||
|
||||
server->networked.socket = -1;
|
||||
server->state = SERVER_STATE_STOPPED;
|
||||
consolePrint("Server stopped.");
|
||||
}
|
||||
|
||||
void * networkedServerThread(void *arg) {
|
||||
assertNotNull(arg, "Server is null");
|
||||
assertNotMainThread("Server thread must not be main thread");
|
||||
|
||||
server_t *server = (server_t *)arg;
|
||||
|
||||
// Set server state to running
|
||||
pthread_mutex_lock(&server->networked.lock);
|
||||
server->state = SERVER_STATE_RUNNING;
|
||||
pthread_mutex_unlock(&server->networked.lock);
|
||||
|
||||
fd_set readfds;
|
||||
struct timeval timeout;
|
||||
|
||||
// Main server loop
|
||||
while (1) {
|
||||
// Check if the server should continue running
|
||||
pthread_mutex_lock(&server->networked.lock);
|
||||
if (server->state != SERVER_STATE_RUNNING) {
|
||||
pthread_mutex_unlock(&server->networked.lock);
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&server->networked.lock);
|
||||
|
||||
// Prepare the select call to wait for new connections
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(server->networked.socket, &readfds);
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
// Wait for incoming connections
|
||||
int activity = select(
|
||||
server->networked.socket + 1,
|
||||
&readfds,
|
||||
NULL,
|
||||
NULL,
|
||||
&timeout
|
||||
);
|
||||
if (activity <= 0) continue;
|
||||
if (!FD_ISSET(server->networked.socket, &readfds)) continue;
|
||||
|
||||
// Accept incoming connection
|
||||
int32_t clientSocket = accept(server->networked.socket, NULL, NULL);
|
||||
if (clientSocket < 0) {
|
||||
consolePrint("Failed to accept connection");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find an available client slot
|
||||
serverclient_t *client = NULL;
|
||||
for (uint8_t i = 0; i < SERVER_MAX_CLIENTS; i++) {
|
||||
if(server->clients[i].state != SERVER_CLIENT_STATE_DISCONNECTED) {
|
||||
continue;
|
||||
}
|
||||
client = &server->clients[i];
|
||||
break;
|
||||
}
|
||||
|
||||
// Server full, send disconnect packet
|
||||
if (!client) {
|
||||
packet_t packet;
|
||||
serverclient_t tempClient = {
|
||||
.networked = { .socket = clientSocket },
|
||||
.server = server,
|
||||
.state = SERVER_CLIENT_STATE_ACCEPTING
|
||||
};
|
||||
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_SERVER_FULL);
|
||||
networkedServerClientWritePacket(&tempClient, &packet);
|
||||
if (errorCheck()) errorPrint();
|
||||
consolePrint("Client disconnected: Server full.");
|
||||
close(clientSocket);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Accept the client into the server
|
||||
errorret_t ret = serverClientAccept(client, (serverclientaccept_t){
|
||||
.server = server,
|
||||
.networked = { .socket = clientSocket }
|
||||
});
|
||||
if (ret != ERROR_OK) {
|
||||
consolePrint("Failed to accept client connection: %s", errorString());
|
||||
errorFlush();
|
||||
close(clientSocket);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup and signal server stop
|
||||
pthread_mutex_lock(&server->networked.lock);
|
||||
server->state = SERVER_STATE_STOPPED;
|
||||
pthread_mutex_unlock(&server->networked.lock);
|
||||
|
||||
consolePrint("Server thread exiting.");
|
||||
return NULL;
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
typedef struct {
|
||||
uint16_t port;
|
||||
} networkedserverstart_t;
|
||||
|
||||
typedef struct {
|
||||
int socket;
|
||||
struct sockaddr_in address;
|
||||
pthread_mutex_t lock;
|
||||
} networkedserver_t;
|
||||
|
||||
typedef struct server_s server_t;
|
||||
typedef struct serverstart_s serverstart_t;
|
||||
|
||||
/**
|
||||
* Initialize the networked server
|
||||
*
|
||||
* This function initializes the networked server by setting up the ENet library
|
||||
* and creating a server host.
|
||||
*
|
||||
* @param server Pointer to the server structure.
|
||||
* @param start Information about how to start the server.
|
||||
* @return Returns an error code if the server fails to start.
|
||||
*/
|
||||
errorret_t networkedServerStart(server_t *server, const serverstart_t start);
|
||||
|
||||
/**
|
||||
* Stop the networked server
|
||||
*
|
||||
* This function stops the networked server by shutting down the ENet library
|
||||
* and closing the server host.
|
||||
*
|
||||
* @param server Pointer to the server structure.
|
||||
*/
|
||||
void networkedServerStop(server_t *server);
|
||||
|
||||
/**
|
||||
* Server thread function
|
||||
*
|
||||
* This function runs in a separate thread and handles incoming connections,
|
||||
* messages, and disconnections.
|
||||
*
|
||||
* @param arg Pointer to the argument passed to the thread.
|
||||
*/
|
||||
void * networkedServerThread(void *arg);
|
@ -1,382 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "network/server/server.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "console/console.h"
|
||||
#include <sys/socket.h>
|
||||
|
||||
errorret_t networkedServerClientAccept(
|
||||
serverclient_t *client,
|
||||
const serverclientaccept_t accept
|
||||
) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(accept.server, "Server is NULL");
|
||||
assertTrue(
|
||||
accept.server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
|
||||
client->state = SERVER_CLIENT_STATE_ACCEPTING;
|
||||
client->networked.socket = accept.networked.socket;
|
||||
|
||||
// Set timeout to 8 seconds
|
||||
client->networked.timeout.tv_sec = 8;
|
||||
client->networked.timeout.tv_usec = 0;
|
||||
|
||||
// Initialize mutexs
|
||||
pthread_mutex_init(&client->networked.readLock, NULL);
|
||||
pthread_mutex_init(&client->networked.writeLock, NULL);
|
||||
|
||||
// Create a read thread for the client
|
||||
int32_t ret = pthread_create(
|
||||
&client->networked.readThread,
|
||||
NULL,
|
||||
networkedServerClientReadThread,
|
||||
client
|
||||
);
|
||||
if(ret != 0) {
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
return error("Failed to create client read thread");
|
||||
}
|
||||
|
||||
// Set socket timeout
|
||||
if(setsockopt(
|
||||
client->networked.socket,
|
||||
SOL_SOCKET,
|
||||
SO_RCVTIMEO,
|
||||
&client->networked.timeout,
|
||||
sizeof(client->networked.timeout)
|
||||
) < 0) {
|
||||
networkedServerClientCloseOnThread(client, "Failed to set socket timeout");
|
||||
return error("Failed to set socket timeout");
|
||||
}
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
void networkedServerClientClose(serverclient_t *client) {
|
||||
assertIsMainThread("Server client close must be on main thread.");
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
|
||||
// Mark client as disconnecting
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
|
||||
|
||||
// Wait for the read thread to finish
|
||||
pthread_mutex_lock(&client->networked.readLock);
|
||||
pthread_mutex_unlock(&client->networked.readLock);
|
||||
pthread_join(client->networked.readThread, NULL);
|
||||
client->networked.readThread = 0;
|
||||
pthread_mutex_destroy(&client->networked.readLock);
|
||||
|
||||
// Signal and wait for the write thread to finish
|
||||
pthread_mutex_lock(&client->networked.writeLock);
|
||||
pthread_mutex_unlock(&client->networked.writeLock);
|
||||
pthread_join(client->networked.writeThread, NULL);
|
||||
client->networked.writeThread = 0;
|
||||
pthread_mutex_destroy(&client->networked.writeLock);
|
||||
|
||||
// Close the socket
|
||||
if(client->networked.socket != -1) {
|
||||
close(client->networked.socket);
|
||||
client->networked.socket = -1;
|
||||
}
|
||||
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
consolePrint("Client %d disconnected.", client->networked.socket);
|
||||
}
|
||||
|
||||
void networkedServerClientCloseOnThread(
|
||||
serverclient_t *client,
|
||||
const char_t *reason
|
||||
) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(reason, "Reason is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
assertNotMainThread("Client close must not be main thread");
|
||||
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
|
||||
|
||||
// Terminate the socket
|
||||
close(client->networked.socket);
|
||||
client->networked.socket = -1;
|
||||
client->networked.readThread = 0;
|
||||
consolePrint("Client %d disconnected: %s", client->networked.socket, reason);
|
||||
|
||||
// Mark this client as disconnected so it can be used again.
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
ssize_t networkedServerClientRead(
|
||||
const serverclient_t * client,
|
||||
uint8_t *buffer,
|
||||
const size_t len
|
||||
) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(buffer, "Buffer is NULL");
|
||||
assertNotNull(client->server, "Server is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
assertTrue(len > 0, "Buffer length is 0");
|
||||
|
||||
assertTrue(
|
||||
client->state == SERVER_CLIENT_STATE_CONNECTED ||
|
||||
client->state == SERVER_CLIENT_STATE_ACCEPTING ||
|
||||
client->state == SERVER_CLIENT_STATE_DISCONNECTING,
|
||||
"Client is not connected, accepting or disconnecting"
|
||||
);
|
||||
|
||||
return recv(client->networked.socket, buffer, len, 0);
|
||||
}
|
||||
|
||||
errorret_t networkedServerClientReadPacket(
|
||||
const serverclient_t * client,
|
||||
packet_t *packet
|
||||
) {
|
||||
uint8_t buffer[sizeof(packet_t)];
|
||||
ssize_t read;
|
||||
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
|
||||
// Read packet ID
|
||||
read = networkedServerClientRead(client, buffer, sizeof(packettype_t));
|
||||
if(read != sizeof(packettype_t)) {
|
||||
return error("Failed to read packet ID");
|
||||
}
|
||||
|
||||
packet->type = *(packettype_t *)buffer;
|
||||
if(packet->type == PACKET_TYPE_INVALID) {
|
||||
return error("Invalid packet type");
|
||||
}
|
||||
|
||||
// Read length
|
||||
read = networkedServerClientRead(
|
||||
client,
|
||||
buffer,
|
||||
sizeof(uint32_t)
|
||||
);
|
||||
|
||||
if(read != sizeof(uint32_t)) {
|
||||
return error("Failed to read packet length");
|
||||
}
|
||||
|
||||
packet->length = *(uint32_t *)buffer;
|
||||
if(packet->length > sizeof(packetdata_t)) {
|
||||
return error("Packet length is too large");
|
||||
}
|
||||
|
||||
// Read data
|
||||
read = networkedServerClientRead(
|
||||
client,
|
||||
(uint8_t *)&packet->data,
|
||||
packet->length
|
||||
);
|
||||
if(read != packet->length) {
|
||||
return error("Failed to read packet data");
|
||||
}
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
errorret_t networkedServerClientWrite(
|
||||
serverclient_t * client,
|
||||
const uint8_t *data,
|
||||
const size_t len
|
||||
) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(data, "Data is NULL");
|
||||
assertTrue(len > 0, "Data length is 0");
|
||||
assertNotNull(client->server, "Server is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
|
||||
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) {
|
||||
return error("Client is disconnected");
|
||||
}
|
||||
ssize_t sent = send(client->networked.socket, data, len, 0);
|
||||
if(sent < 0) return error("Failed to send data");
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
errorret_t networkedServerClientWritePacket(
|
||||
serverclient_t * client,
|
||||
const packet_t *packet
|
||||
) {
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
assertTrue(packet->type != PACKET_TYPE_INVALID, "Packet type is INVALID");
|
||||
assertTrue(packet->length > 0, "Packet length is 0");
|
||||
assertTrue(
|
||||
packet->length <= sizeof(packetdata_t),
|
||||
"Packet length is too large (1)"
|
||||
);
|
||||
|
||||
size_t fullSize = sizeof(packet_t) - sizeof(packet->data) + packet->length;
|
||||
assertTrue(fullSize <= sizeof(packet_t), "Packet size is too large (2)");
|
||||
return networkedServerClientWrite(client, (const uint8_t *)packet, fullSize);
|
||||
}
|
||||
|
||||
void * networkedServerClientReadThread(void *arg) {
|
||||
assertNotNull(arg, "Client is NULL");
|
||||
assertNotMainThread("Client thread must not be main thread");
|
||||
|
||||
serverclient_t *client = (serverclient_t *)arg;
|
||||
char_t buffer[1024];
|
||||
ssize_t read;
|
||||
errorret_t err;
|
||||
packet_t packet;
|
||||
|
||||
assertNotNull(client->server, "Server is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
assertTrue(
|
||||
client->state == SERVER_CLIENT_STATE_ACCEPTING,
|
||||
"Client is not accepting?"
|
||||
);
|
||||
|
||||
// First message from the client should always be "DUSK|VERSION" to match
|
||||
// the server version.
|
||||
{
|
||||
const char_t *expecting = "DUSK|"DUSK_VERSION;
|
||||
read = networkedServerClientRead(client, buffer, sizeof(buffer));
|
||||
if(read <= 0) {
|
||||
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
|
||||
err = networkedServerClientWritePacket(client, &packet);
|
||||
networkedServerClientCloseOnThread(client, "Failed to receive version");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buffer[read] = '\0'; // Null-terminate the string
|
||||
if(strncmp(buffer, expecting, strlen(expecting)) != 0) {
|
||||
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
|
||||
err = networkedServerClientWritePacket(client, &packet);
|
||||
networkedServerClientCloseOnThread(client, "Invalid version");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Send DUSK back!
|
||||
packetWelcomeCreate(&packet);
|
||||
err = networkedServerClientWritePacket(client, &packet);
|
||||
if(err != ERROR_OK) {
|
||||
networkedServerClientCloseOnThread(
|
||||
client, "Failed to send welcome message"
|
||||
);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Client is connected.
|
||||
client->state = SERVER_CLIENT_STATE_CONNECTED;
|
||||
|
||||
// Start the write thread after the handshake
|
||||
int32_t ret = pthread_create(
|
||||
&client->networked.writeThread,
|
||||
NULL,
|
||||
networkedServerClientWriteThread,
|
||||
client
|
||||
);
|
||||
if(ret != 0) {
|
||||
networkedServerClientCloseOnThread(client, "Failed to create write thread");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Start listening for packets.
|
||||
while(client->state == SERVER_CLIENT_STATE_CONNECTED) {
|
||||
pthread_mutex_lock(&client->networked.readLock);
|
||||
|
||||
err = networkedServerClientReadPacket(client, &packet);
|
||||
if(err != ERROR_OK) {
|
||||
consolePrint("Failed to read packet %s", errorString());
|
||||
errorFlush();
|
||||
pthread_mutex_unlock(&client->networked.readLock);
|
||||
break;
|
||||
}
|
||||
|
||||
packetQueuePushIn(&client->packetQueue, &packet);
|
||||
|
||||
pthread_mutex_unlock(&client->networked.readLock);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&client->networked.readLock);
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
pthread_mutex_unlock(&client->networked.readLock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void * networkedServerClientWriteThread(void *arg) {
|
||||
packet_t packet;
|
||||
int32_t ret;
|
||||
errorret_t err;
|
||||
|
||||
assertNotNull(arg, "Client is NULL");
|
||||
assertNotMainThread("Client thread must not be main thread");
|
||||
assertTrue(
|
||||
((serverclient_t *)arg)->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
assertTrue(
|
||||
((serverclient_t *)arg)->state == SERVER_CLIENT_STATE_CONNECTED,
|
||||
"Client is not connected"
|
||||
);
|
||||
|
||||
serverclient_t *client = (serverclient_t *)arg;
|
||||
|
||||
while(client->state == SERVER_CLIENT_STATE_CONNECTED) {
|
||||
pthread_mutex_lock(&client->networked.writeLock);
|
||||
|
||||
ret = packetQueuePopOut(
|
||||
&client->packetQueue,
|
||||
&packet
|
||||
);
|
||||
|
||||
if(ret == 0) {
|
||||
pthread_mutex_unlock(&client->networked.writeLock);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ret < 0) {
|
||||
consolePrint("Failed to pop packet %s", errorString());
|
||||
errorFlush();
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
|
||||
pthread_mutex_unlock(&client->networked.writeLock);
|
||||
break;
|
||||
}
|
||||
|
||||
err = networkedServerClientWritePacket(client, &packet);
|
||||
if(err != ERROR_OK) {
|
||||
consolePrint("Failed to write packet %s", errorString());
|
||||
errorFlush();
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
|
||||
pthread_mutex_unlock(&client->networked.writeLock);
|
||||
break;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&client->networked.writeLock);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
typedef struct serverclient_s serverclient_t;
|
||||
typedef struct serverclientaccept_s serverclientaccept_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t socket;
|
||||
} networkedserverclientaccept_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t socket;
|
||||
pthread_t readThread;
|
||||
pthread_t writeThread;
|
||||
struct timeval timeout;
|
||||
pthread_mutex_t readLock;
|
||||
pthread_mutex_t writeLock;
|
||||
} networkedserverclient_t;
|
||||
|
||||
/**
|
||||
* Accepts an incoming connection from a networked server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param accept Accept structure containing client information.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
errorret_t networkedServerClientAccept(
|
||||
serverclient_t *client,
|
||||
const serverclientaccept_t accept
|
||||
);
|
||||
|
||||
/**
|
||||
* Closes the connection to a networked server client. Waits for the thread to finish.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
*/
|
||||
void networkedServerClientClose(serverclient_t *client);
|
||||
|
||||
/**
|
||||
* Closes the connection to a networked server client on the thread.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param reason Reason for closing the connection.
|
||||
*/
|
||||
void networkedServerClientCloseOnThread(
|
||||
serverclient_t *client,
|
||||
const char_t *reason
|
||||
);
|
||||
|
||||
/**
|
||||
* Receives data from a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param buffer Pointer to the buffer to store received data.
|
||||
* @param len Max length of the buffer.
|
||||
* @return Number of bytes received. 0 or less indicates an error.
|
||||
*/
|
||||
ssize_t networkedServerClientRead(
|
||||
const serverclient_t * client,
|
||||
uint8_t *buffer,
|
||||
const size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Writes data to a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param data Pointer to the data to send.
|
||||
* @param len Length of the data to send.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
errorret_t networkedServerClientWrite(
|
||||
serverclient_t * client,
|
||||
const uint8_t *data,
|
||||
const size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Writes a packet to a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param packet Pointer to the packet to send.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
errorret_t networkedServerClientWritePacket(
|
||||
serverclient_t * client,
|
||||
const packet_t *packet
|
||||
);
|
||||
|
||||
/**
|
||||
* Thread function for handling reading from a networked server client.
|
||||
*
|
||||
* @param arg Pointer to the server client structure.
|
||||
* @return NULL.
|
||||
*/
|
||||
void * networkedServerClientReadThread(void *arg);
|
||||
|
||||
/**
|
||||
* Thread function for handling writing to a networked server client.
|
||||
*
|
||||
* @param arg Pointer to the server client structure.
|
||||
* @return NULL.
|
||||
*/
|
||||
void * networkedServerClientWriteThread(void *arg);
|
@ -1,127 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "console/console.h"
|
||||
#include "util/string.h"
|
||||
|
||||
server_t SERVER;
|
||||
|
||||
void cmdStart(const consolecmdexec_t *exec) {
|
||||
serverstart_t start;
|
||||
|
||||
if(exec->argc > 0) {
|
||||
if(stringCompare(exec->argv[0], "0") == 0) {
|
||||
start.type = SERVER_TYPE_SINGLE_PLAYER;
|
||||
} else if(stringCompare(exec->argv[0], "1") == 0) {
|
||||
start.type = SERVER_TYPE_NETWORKED;
|
||||
} else {
|
||||
consolePrint("Invalid server type: %s", exec->argv[0]);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
start.type = SERVER_TYPE_NETWORKED;
|
||||
}
|
||||
|
||||
if(exec->argc > 1) {
|
||||
if(start.type == SERVER_TYPE_NETWORKED) {
|
||||
if(!stringToU16(exec->argv[1], &start.networked.port)) {
|
||||
consolePrint("Invalid port number: %s", exec->argv[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(start.type == SERVER_TYPE_NETWORKED) {
|
||||
start.networked.port = SERVER_DEFAULT_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t ret = serverStart(start);
|
||||
if(ret != ERROR_OK) {
|
||||
consolePrint("Failed to start server: %s", errorString());
|
||||
errorFlush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void cmdClose(const consolecmdexec_t *exec) {
|
||||
serverStop();
|
||||
}
|
||||
|
||||
void serverInit() {
|
||||
memoryZero(&SERVER, sizeof(server_t));
|
||||
SERVER.state = SERVER_STATE_STOPPED;
|
||||
|
||||
consoleRegCmd("start", cmdStart);
|
||||
consoleRegCmd("close", cmdClose);
|
||||
}
|
||||
|
||||
errorret_t serverStart(const serverstart_t start) {
|
||||
errorret_t ret;
|
||||
assertIsMainThread("Server start must be on main thread");
|
||||
|
||||
// Do not start a running server.
|
||||
if(SERVER.state != SERVER_STATE_STOPPED) {
|
||||
return error("Server is already running");
|
||||
}
|
||||
|
||||
SERVER.type = start.type;
|
||||
|
||||
// Hand off to relevant server type to start.
|
||||
switch(start.type) {
|
||||
case SERVER_TYPE_NETWORKED:
|
||||
ret = networkedServerStart(&SERVER, start);
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid server type");
|
||||
}
|
||||
|
||||
if(ret != ERROR_OK) SERVER.state = SERVER_STATE_STOPPED;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t serverGetClientCount() {
|
||||
uint8_t i = 0, clientCount = 0;
|
||||
do {
|
||||
if(SERVER.clients[i++].state == SERVER_CLIENT_STATE_DISCONNECTED) continue;
|
||||
clientCount++;
|
||||
} while(i < SERVER_MAX_CLIENTS);
|
||||
return clientCount;
|
||||
}
|
||||
|
||||
void serverUpdate() {
|
||||
assertIsMainThread("Server update must be on main thread");
|
||||
if(SERVER.state != SERVER_STATE_RUNNING) return;
|
||||
|
||||
// Update all clients
|
||||
for(uint8_t i = 0; i < SERVER_MAX_CLIENTS; i++) {
|
||||
serverClientUpdate(&SERVER.clients[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void serverStop() {
|
||||
assertIsMainThread("Server stop must be on main thread");
|
||||
if(SERVER.state == SERVER_STATE_STOPPED) return;
|
||||
|
||||
consolePrint("Stopping server...");
|
||||
switch(SERVER.type) {
|
||||
case SERVER_TYPE_NETWORKED:
|
||||
networkedServerStop(&SERVER);
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid server type");
|
||||
}
|
||||
consolePrint("Server stopped.");
|
||||
}
|
||||
|
||||
void serverDispose() {
|
||||
serverStop();
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "serverclient.h"
|
||||
#include "network/server/networked/networkedserver.h"
|
||||
|
||||
#define SERVER_MAX_CLIENTS 32
|
||||
#define SERVER_DEFAULT_PORT 3030
|
||||
|
||||
typedef enum {
|
||||
SERVER_STATE_STOPPED,
|
||||
SERVER_STATE_STARTING,
|
||||
SERVER_STATE_RUNNING,
|
||||
SERVER_STATE_STOPPING
|
||||
} serverstate_t;
|
||||
|
||||
typedef enum {
|
||||
SERVER_TYPE_SINGLE_PLAYER,
|
||||
SERVER_TYPE_NETWORKED
|
||||
} servertype_t;
|
||||
|
||||
typedef struct serverstart_s {
|
||||
servertype_t type;
|
||||
union {
|
||||
networkedserverstart_t networked;
|
||||
};
|
||||
} serverstart_t;
|
||||
|
||||
typedef struct server_s {
|
||||
serverstate_t state;
|
||||
servertype_t type;
|
||||
pthread_t thread;
|
||||
serverclient_t clients[SERVER_MAX_CLIENTS];
|
||||
|
||||
union {
|
||||
networkedserver_t networked;
|
||||
};
|
||||
} server_t;
|
||||
|
||||
extern server_t SERVER;
|
||||
|
||||
/**
|
||||
* Initialize the server
|
||||
*
|
||||
* This function initializes the server by setting up the ENet library and
|
||||
* creating a server host.
|
||||
*/
|
||||
void serverInit();
|
||||
|
||||
/**
|
||||
* Start the server
|
||||
*
|
||||
* This function starts the server by creating a host, binding it to the
|
||||
* specified address and port, and starting the server thread.
|
||||
*
|
||||
* @param port The port number to bind the server to.
|
||||
* @return Returns an error code if the server fails to start.
|
||||
*/
|
||||
errorret_t serverStart(const serverstart_t start);
|
||||
|
||||
/**
|
||||
* Get the client count
|
||||
*
|
||||
* This function returns the number of connected clients to the server.
|
||||
*
|
||||
* @return The number of connected clients.
|
||||
*/
|
||||
uint8_t serverGetClientCount();
|
||||
|
||||
/**
|
||||
* Performs a (main thread) update to process clients on the server.
|
||||
*/
|
||||
void serverUpdate();
|
||||
|
||||
/**
|
||||
* Stop the server
|
||||
*
|
||||
* This function stops the server by destroying the host and cleaning up
|
||||
* resources.
|
||||
*/
|
||||
void serverStop();
|
||||
|
||||
/**
|
||||
* Dispose of the server
|
||||
*
|
||||
* This function disposes of the server by deinitializing the ENet library.
|
||||
*/
|
||||
void serverDispose();
|
@ -1,79 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "console/console.h"
|
||||
|
||||
errorret_t serverClientAccept(
|
||||
serverclient_t *client,
|
||||
const serverclientaccept_t accept
|
||||
) {
|
||||
errorret_t ret;
|
||||
memoryZero(client, sizeof(serverclient_t));
|
||||
assertNotNull(accept.server, "Server is NULL");
|
||||
assertNotMainThread("Server client accept must not be main thread");
|
||||
|
||||
client->server = accept.server;
|
||||
packetQueueInit(&client->packetQueue);
|
||||
|
||||
switch(accept.server->type) {
|
||||
case SERVER_TYPE_NETWORKED:
|
||||
ret = networkedServerClientAccept(client, accept);
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Unknown server type");
|
||||
}
|
||||
|
||||
if(ret != ERROR_OK) memoryZero(client, sizeof(serverclient_t));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void serverClientUpdate(serverclient_t *client) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertIsMainThread("Server client update must be on main thread");
|
||||
|
||||
if(client->state != SERVER_CLIENT_STATE_CONNECTED) return;
|
||||
|
||||
packet_t packet;
|
||||
int32_t ret = 1;
|
||||
|
||||
// Process packets
|
||||
do {
|
||||
ret = packetQueuePopIn(
|
||||
&client->packetQueue,
|
||||
&packet
|
||||
);
|
||||
|
||||
if(ret == 0) break;
|
||||
|
||||
if(ret < 0) {
|
||||
serverClientClose(client);
|
||||
consolePrint("Failed to pop packet");
|
||||
return;
|
||||
}
|
||||
|
||||
packetServerProcess(&packet, client);
|
||||
} while(true);
|
||||
}
|
||||
|
||||
void serverClientClose(serverclient_t *client) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertIsMainThread("Server client close must be on main thread");
|
||||
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) return;
|
||||
|
||||
switch(client->server->type) {
|
||||
case SERVER_TYPE_NETWORKED:
|
||||
networkedServerClientClose(client);
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Unknown server type");
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "network/packet/packet.h"
|
||||
#include "network/server/networked/networkedserverclient.h"
|
||||
#include "network/packet/packetqueue.h"
|
||||
|
||||
typedef struct server_s server_t;
|
||||
|
||||
typedef enum {
|
||||
SERVER_CLIENT_STATE_DISCONNECTED,
|
||||
SERVER_CLIENT_STATE_ACCEPTING,
|
||||
SERVER_CLIENT_STATE_CONNECTED,
|
||||
SERVER_CLIENT_STATE_DISCONNECTING,
|
||||
} serverclientstate_t;
|
||||
|
||||
typedef struct serverclientaccept_s {
|
||||
server_t *server;
|
||||
union {
|
||||
networkedserverclientaccept_t networked;
|
||||
};
|
||||
} serverclientaccept_t;
|
||||
|
||||
typedef struct serverclient_s {
|
||||
server_t *server;
|
||||
serverclientstate_t state;
|
||||
packetqueue_t packetQueue;
|
||||
|
||||
union {
|
||||
networkedserverclient_t networked;
|
||||
};
|
||||
} serverclient_t;
|
||||
|
||||
/**
|
||||
* Accepts an incoming connection from a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param accept Accept structure containing client information.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
errorret_t serverClientAccept(
|
||||
serverclient_t *client,
|
||||
const serverclientaccept_t accept
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles the main thread updating of this server client. This is where all
|
||||
* the packets will be handled and processed.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
*/
|
||||
void serverClientUpdate(serverclient_t *client);
|
||||
|
||||
/**
|
||||
* Closes the connection to a server client. Waits for the thread to finish.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
*/
|
||||
void serverClientClose(serverclient_t *client);
|
@ -6,8 +6,9 @@
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
game.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(quest)
|
@ -7,4 +7,5 @@
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
entity.c
|
||||
player.c
|
||||
)
|
34
src/rpg/entity/entity.c
Normal file
34
src/rpg/entity/entity.c
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "entity.h"
|
||||
|
||||
entity_t ENTITIES[ENTITY_COUNT];
|
||||
|
||||
entitycallbacks_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
|
||||
[ENTITY_TYPE_NULL] = {
|
||||
.init = NULL
|
||||
},
|
||||
|
||||
[ENTITY_TYPE_PLAYER] = {
|
||||
.init = playerInit
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
void entityInit(entity_t *entity, const entitytype_t type) {
|
||||
assertNotNull(entity, "Entity is NULL");
|
||||
assertTrue(type < ENTITY_TYPE_COUNT, "Invalid entity type");
|
||||
assertNotNull(ENTITY_CALLBACKS[type].init, "Entity type has no init");
|
||||
|
||||
memoryZero(entity, sizeof(entity_t));
|
||||
entity->type = type;
|
||||
ENTITY_CALLBACKS[type].init(entity);
|
||||
}
|
54
src/rpg/entity/entity.h
Normal file
54
src/rpg/entity/entity.h
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "player.h"
|
||||
|
||||
typedef enum {
|
||||
ENTITY_DIR_UP = 0,
|
||||
ENTITY_DIR_DOWN = 1,
|
||||
ENTITY_DIR_LEFT = 2,
|
||||
ENTITY_DIR_RIGHT = 3,
|
||||
|
||||
ENTITY_DIR_NORTH = ENTITY_DIR_UP,
|
||||
ENTITY_DIR_SOUTH = ENTITY_DIR_DOWN,
|
||||
ENTITY_DIR_WEST = ENTITY_DIR_LEFT,
|
||||
ENTITY_DIR_EAST = ENTITY_DIR_RIGHT
|
||||
} entitydir_t;
|
||||
|
||||
typedef enum {
|
||||
ENTITY_TYPE_NULL = 0,
|
||||
ENTITY_TYPE_PLAYER = 1,
|
||||
} entitytype_t;
|
||||
#define ENTITY_TYPE_COUNT (ENTITY_TYPE_PLAYER + 1)
|
||||
|
||||
typedef struct _entity_t {
|
||||
entitytype_t type;
|
||||
uint8_t x, y;
|
||||
entitydir_t dir;
|
||||
|
||||
// Per type data
|
||||
union {
|
||||
player_t player;
|
||||
};
|
||||
} entity_t;
|
||||
|
||||
#define ENTITY_COUNT 16
|
||||
extern entity_t ENTITIES[ENTITY_COUNT];
|
||||
|
||||
typedef struct {
|
||||
void (*init)(entity_t *entity);
|
||||
} entitycallbacks_t;
|
||||
extern entitycallbacks_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes an entity with the given type.
|
||||
*
|
||||
* @param entity Pointer to the entity to initialize.
|
||||
* @param type The type of the entity to initialize.
|
||||
*/
|
||||
void entityInit(entity_t *entity, const entitytype_t type);
|
14
src/rpg/entity/player.c
Normal file
14
src/rpg/entity/player.c
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void playerInit(entity_t *player) {
|
||||
assertNotNull(player, "Player entity is NULL");
|
||||
assertTrue(player->type == ENTITY_TYPE_PLAYER, "Entity is not a player");
|
||||
}
|
22
src/rpg/entity/player.h
Normal file
22
src/rpg/entity/player.h
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct _entity_t entity_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t nothing;
|
||||
} player_t;
|
||||
|
||||
/**
|
||||
* Initializes a player entity.
|
||||
*
|
||||
* @param ent Pointer to the player entity to initialize.
|
||||
*/
|
||||
void playerInit(entity_t *ent);
|
@ -9,6 +9,5 @@
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
uint8_t width, height;
|
||||
} map_t;
|
||||
|
||||
} event_t;
|
@ -6,5 +6,5 @@
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
error.c
|
||||
itemtype.c
|
||||
)
|
9
src/rpg/item/item.h
Normal file
9
src/rpg/item/item.h
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "itemtype.h"
|
22
src/rpg/item/itemtype.c
Normal file
22
src/rpg/item/itemtype.c
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "itemtype.h"
|
||||
|
||||
iteminfo_t ITEM_INFO[ITEM_TYPE_COUNT] = {
|
||||
[ITEM_TYPE_NULL] = { 0 },
|
||||
|
||||
// Consumables
|
||||
[ITEM_TYPE_POTION] = { .stackable = true },
|
||||
|
||||
// Ingredients
|
||||
[ITEM_TYPE_ONION] = { .stackable = true },
|
||||
[ITEM_TYPE_SWEET_POTATO] = { .stackable = true },
|
||||
|
||||
// Cooked Items
|
||||
[ITEM_TYPE_BAKED_SWEET_POTATO] = { .stackable = true },
|
||||
};
|
30
src/rpg/item/itemtype.h
Normal file
30
src/rpg/item/itemtype.h
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
ITEM_TYPE_NULL,
|
||||
|
||||
// Consumables
|
||||
ITEM_TYPE_POTION,
|
||||
|
||||
// Ingredients
|
||||
ITEM_TYPE_ONION,
|
||||
ITEM_TYPE_SWEET_POTATO,
|
||||
|
||||
// Cooked Items
|
||||
ITEM_TYPE_BAKED_SWEET_POTATO,
|
||||
} itemtype_t;
|
||||
|
||||
#define ITEM_TYPE_COUNT (ITEM_TYPE_BAKED_SWEET_POTATO + 1)
|
||||
|
||||
typedef struct {
|
||||
bool_t stackable;
|
||||
} iteminfo_t;
|
||||
extern iteminfo_t ITEM_INFO[ITEM_TYPE_COUNT];
|
@ -6,4 +6,5 @@
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
quest.c
|
||||
)
|
@ -5,4 +5,4 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "networkclient.h"
|
||||
#include "quest.h"
|
@ -6,8 +6,8 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct client_s client_t;
|
||||
typedef struct serverclient_s serverclient_t;
|
||||
typedef struct packet_s packet_t;
|
||||
typedef struct {
|
||||
|
||||
} quest_t;
|
@ -8,17 +8,6 @@
|
||||
#include "memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void * memoryAllocate(const size_t size) {
|
||||
void *ptr = malloc(size);
|
||||
assertNotNull(ptr, "Failed to allocate memory.");
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void memoryFree(void *ptr) {
|
||||
assertNotNull(ptr, "Cannot free NULL memory.");
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
void memoryCopy(void *dest, const void *src, const size_t size) {
|
||||
assertNotNull(dest, "Cannot copy to NULL memory.");
|
||||
assertNotNull(src, "Cannot copy from NULL memory.");
|
||||
|
@ -9,19 +9,4 @@
|
||||
#include "assert/assert.h"
|
||||
|
||||
void randomInit() {
|
||||
randomSeed(time(NULL));
|
||||
}
|
||||
|
||||
void randomSeed(const uint32_t seed) {
|
||||
srand(seed);
|
||||
}
|
||||
|
||||
int32_t randomI32(const int32_t min, const int32_t max) {
|
||||
assertTrue(min < max, "Min is not less than max");
|
||||
return (rand() % (max - min)) + min;
|
||||
}
|
||||
|
||||
float_t randomF32(const float_t min, const float_t max) {
|
||||
assertTrue(min < max, "Min is not less than max");
|
||||
return ((float_t)rand() / (float_t)RAND_MAX) * (max - min) + min;
|
||||
}
|
@ -15,29 +15,4 @@
|
||||
* initialize the random number generator with a random seed. It uses
|
||||
* the current time to generate a seed.
|
||||
*/
|
||||
void randomInit();
|
||||
|
||||
/**
|
||||
* Sets the random seed for the random number generator.
|
||||
*
|
||||
* @param seed The seed to set.
|
||||
*/
|
||||
void randomSeed(const uint32_t seed);
|
||||
|
||||
/**
|
||||
* Generates a random integer between min and max.
|
||||
*
|
||||
* @param min The minimum value (inclusive).
|
||||
* @param max The maximum value (exclusive).
|
||||
* @return A random integer between min and max.
|
||||
*/
|
||||
int32_t randomI32(const int32_t min, const int32_t max);
|
||||
|
||||
/**
|
||||
* Generates a random float between min and max.
|
||||
*
|
||||
* @param min The minimum value (inclusive).
|
||||
* @param max The maximum value (exclusive).
|
||||
* @return A random float between min and max.
|
||||
*/
|
||||
float_t randomF32(const float_t min, const float_t max);
|
||||
void randomInit();
|
Reference in New Issue
Block a user