This commit is contained in:
2025-08-05 15:59:12 -05:00
parent 577bef8fb7
commit d68fae0db7
30 changed files with 1389 additions and 42 deletions

View File

@@ -7,9 +7,11 @@
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
if(NOT DEFINED DUSK_TARGET_SYSTEM)
set(DUSK_TARGET_SYSTEM "raylib")
set(DUSK_TARGET_SYSTEM "linux")
# set(DUSK_TARGET_SYSTEM "psp")
endif()
# Prep cache
@@ -35,6 +37,11 @@ set(DUSK_ASSETS "" CACHE INTERNAL ${DUSK_CACHE_TARGET})
file(MAKE_DIRECTORY ${DUSK_GENERATED_HEADERS_DIR})
file(MAKE_DIRECTORY ${DUSK_ASSETS_BUILD_DIR})
# Compilers
if(DUSK_TARGET_SYSTEM STREQUAL "psp")
find_package(pspsdk REQUIRED)
endif()
# Init Project
project(${DUSK_TARGET_NAME}
VERSION 1.0.0
@@ -55,3 +62,15 @@ target_include_directories(${DUSK_TARGET_NAME}
PUBLIC
${DUSK_GENERATED_HEADERS_DIR}
)
# Postbuild, create PBP file for PSP.
if(DUSK_TARGET_SYSTEM STREQUAL "psp")
create_pbp_file(
TARGET "${DUSK_TARGET_NAME}"
ICON_PATH NULL
BACKGROUND_PATH NULL
PREVIEW_PATH NULL
TITLE "${DUSK_TARGET_NAME}"
VERSION 01.00
)
endif()

View File

@@ -0,0 +1,99 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
if(NOT TARGET pspsdk)
message(STATUS "Looking for PSPSDK...")
set(PSPSDK_FOUND FALSE CACHE INTERNAL "PSPSDK found")
set(PSPSDK_DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/_pspsdk")
set(PSPSDK_SEARCH_ROOTS
"${PSPSDK_ROOT}"
"$ENV{PSPDEV}"
"$ENV{HOME}/pspdev"
"/usr/local/pspdev"
"/opt/pspdev"
"/usr/pspdev"
"${PSPSDK_DOWNLOAD_DIR}/pspdev"
)
foreach(root IN LISTS PSPSDK_SEARCH_ROOTS)
list(APPEND PSPSDK_BIN_HINTS "${root}/bin")
list(APPEND PSPSDK_INCLUDE_HINTS "${root}/include")
list(APPEND PSPSDK_LIB_HINTS "${root}/lib")
endforeach()
# Find PSP GCC
find_program(PSPSDK_PSP_GCC NAMES psp-gcc HINTS ${PSPSDK_BIN_HINTS})
# If we did not find it, download it.
if(NOT PSPSDK_PSP_GCC)
message(STATUS "psp-gcc not found in system paths. Downloading PSPSDK tarball...")
file(DOWNLOAD
"https://github.com/pspdev/pspdev/releases/latest/download/pspdev-ubuntu-latest-x86_64.tar.gz"
"${CMAKE_BINARY_DIR}/pspsdk.tar.gz"
EXPECTED_HASH SHA256=afe338e92f6eeec21c56a64eeb65201e6b95ecbae938ae38688bbf0f17b69ead
SHOW_PROGRESS
)
# Make output dir
file(MAKE_DIRECTORY "${PSPSDK_DOWNLOAD_DIR}")
# Extract the tarball
execute_process(
COMMAND
${CMAKE_COMMAND} -E tar xzf "${CMAKE_BINARY_DIR}/pspsdk.tar.gz"
WORKING_DIRECTORY
"${PSPSDK_DOWNLOAD_DIR}"
RESULT_VARIABLE
tar_result
)
if(NOT tar_result EQUAL 0)
message(FATAL_ERROR "Failed to extract PSPSDK tarball")
endif()
# Retry discovery with extracted fallback
find_program(PSPSDK_PSP_GCC NAMES psp-gcc HINTS ${PSPSDK_BIN_HINTS})
endif()
if(PSPSDK_PSP_GCC)
get_filename_component(PSPSDK_BIN_ROOT "${PSPSDK_PSP_GCC}" DIRECTORY)
get_filename_component(PSPSDK_ROOT "${PSPSDK_BIN_ROOT}" DIRECTORY)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(ENV{PSPDEV} "${PSPSDK_ROOT}")
set(CMAKE_TOOLCHAIN_FILE "${PSPSDK_ROOT}/psp/share/pspdev.cmake")
set(BUILD_PRX ON CACHE BOOL "Build PRX modules")
include("${PSPSDK_ROOT}/psp/share/pspdev.cmake")
set(CMAKE_C_COMPILER ${PSPSDK_BIN_ROOT}/psp-gcc)
set(CMAKE_CXX_COMPILER ${PSPSDK_BIN_ROOT}/psp-g++)
if(NOT DEFINED PSP)
message(FATAL_ERROR "PSP environment variable is not set correctly.")
endif()
add_library(pspsdk INTERFACE IMPORTED)
set_target_properties(pspsdk PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${PSPSDK_INCLUDE_DIR}"
INTERFACE_LINK_DIRECTORIES "${PSPSDK_LIB_DIR}"
)
target_include_directories(pspsdk
INTERFACE
${PSPDEV}/psp/include
${PSPDEV}/psp/sdk/include
)
target_link_directories(pspsdk
INTERFACE
${PSPDEV}/lib
${PSPDEV}/psp/lib
${PSPDEV}/psp/sdk/lib
)
target_link_libraries(pspsdk INTERFACE
pspdebug
pspdisplay
pspge
pspctrl
)
endif()
endif()

View File

@@ -0,0 +1,20 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include(FetchContent)
FetchContent_Declare(
raylib
URL https://github.com/raysan5/raylib/archive/refs/tags/5.5.tar.gz
URL_HASH MD5=61638c4c2c097fbca1d6a71e4da36c16
)
FetchContent_GetProperties(raylib)
FetchContent_MakeAvailable(raylib)
# Define expected target if not already defined
if(NOT TARGET raylib)
set(raylib_FOUND FALSE CACHE INTERNAL "Whether raylib was found")
else()
set(raylib_FOUND TRUE CACHE INTERNAL "Whether raylib was found")
endif()

View File

@@ -5,6 +5,10 @@
add_subdirectory(dusk)
if(DUSK_TARGET_SYSTEM STREQUAL "raylib")
if(DUSK_TARGET_SYSTEM STREQUAL "linux")
add_subdirectory(duskraylib)
elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
add_subdirectory(duskpsp)
else()
message(FATAL_ERROR "Unsupported target system: ${DUSK_TARGET_SYSTEM}")
endif()

View File

@@ -10,19 +10,20 @@ target_link_libraries(${DUSK_TARGET_NAME}
# Includes
target_include_directories(${DUSK_TARGET_NAME}
PUBLIC
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
main.c
game.c
input.c
)
# Subdirs
add_subdirectory(assert)
add_subdirectory(console)
add_subdirectory(display)
add_subdirectory(entity)
add_subdirectory(event)

View File

@@ -0,0 +1,12 @@
# 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
)

395
src/dusk/console/console.c Normal file
View File

@@ -0,0 +1,395 @@
/**
* 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
);
}

114
src/dusk/console/console.h Normal file
View File

@@ -0,0 +1,114 @@
/**
* 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);

View File

@@ -0,0 +1,27 @@
/**
* 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;
}

View File

@@ -0,0 +1,39 @@
/**
* 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
);

View File

@@ -0,0 +1,21 @@
/**
* 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

View File

@@ -0,0 +1,64 @@
/**
* 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;
}

View File

@@ -0,0 +1,65 @@
/**
* 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);

View File

@@ -27,10 +27,3 @@ void renderDraw(void);
* Disposes of the rendering system.
*/
void renderDispose(void);
/**
* Checks if the rendering system should exit.
*
* @return true if the rendering system should exit, false otherwise.
*/
bool_t renderShouldExit();

View File

@@ -5,7 +5,7 @@
* https://opensource.org/licenses/MIT
*/
#include "display/render.h"
#include "game.h"
#include "util/memory.h"
#include "world/chunk.h"
#include "display/scene.h"
@@ -14,28 +14,23 @@
#include "event/event.h"
#include "ui/uitextbox.h"
// Press F5 to compile and run the compiled game.gb in the Emulicous Emulator/Debugger
void main(void) {
renderInit();
void gameInit(void) {
inputInit();
eventInit();
uiTextboxInit();
overworldInit();
SCENE_CURRENT = SCENE_OVERWORLD;
}
while(!renderShouldExit()) {
RENDER_FRAME++;
renderDraw();
void gameUpdate(void) {
overworldUpdate();
uiTextboxUpdate();
eventUpdate();
// Update input for next frame.
inputUpdate();
}
renderDispose();
void gameDispose(void) {
}

42
src/dusk/game.h Normal file
View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
/**
* Initializes the game, this should be called before any other game functions.
* This should be called by the parent platform at a time that it deems
* appropriate. Any game systems cannot be used until this function has
* been called.
*
* By the point this is called, we expect;
* - Rendering has initialized and is ready to draw.
* - Input has been initialized and is ready to be read.
* - If your system handles time dynamically, it should be ready to be used.
*
* The systems called (in order) are;
* - Input system (Not the platforms input, but the game's input system).
* - Time system (if applicable).
* - Event System
* - UI Systems.
* - Gameplay systems.
*/
void gameInit(void);
/**
* Asks the game to update, this will not do any drawing and should be called
* in the main loop of the system, ideally either after or before the rendering
* has occured.
*/
void gameUpdate(void);
/**
* Cleans up resources used by the game, rendering really should still be
* available at this point because we want to cleanup nicely.
*/
void gameDispose(void);

View File

@@ -8,4 +8,5 @@ target_sources(${DUSK_TARGET_NAME}
PRIVATE
fixed.c
memory.c
string.c
)

127
src/dusk/util/string.c Normal file
View File

@@ -0,0 +1,127 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "string.h"
#include "assert/assert.h"
#include "util/memory.h"
bool_t stringIsWhitespace(const char_t c) {
return isspace(c);
}
void stringCopy(char_t *dest, const char_t *src, const size_t destSize) {
assertNotNull(dest, "dest must not be NULL");
assertNotNull(src, "src must not be NULL");
assertTrue(destSize > 0, "destSize must be greater than 0");
assertStrLenMax(src, destSize, "src is too long");
memoryCopy(dest, src, strlen(src) + 1);
}
int stringCompare(const char_t *str1, const char_t *str2) {
assertNotNull(str1, "str1 must not be NULL");
assertNotNull(str2, "str2 must not be NULL");
return strcmp(str1, str2);
}
void stringTrim(char_t *str) {
assertNotNull(str, "str must not be NULL");
// Trim leading whitespace
char_t *start = str;
while(stringIsWhitespace(*start)) start++;
// Trim trailing whitespace
char_t *end = start + strlen(start) - 1;
while (end >= start && stringIsWhitespace(*end)) end--;
// Null-terminate the string
*(end + 1) = '\0';
// Move trimmed string to the original buffer
if (start != str) memmove(str, start, end - start + 2);
}
char_t * stringToken(char_t *str, const char_t *delim) {
assertNotNull(str, "str must not be NULL");
assertNotNull(delim, "delim must not be NULL");
return strtok(str, delim);
}
int32_t stringFormat(
char_t *dest,
const size_t destSize,
char_t *format,
...
) {
assertNotNull(dest, "dest must not be NULL");
assertNotNull(format, "format must not be NULL");
assertTrue(destSize > 0, "destSize must be greater than 0");
va_list args;
va_start(args, format);
int32_t result = stringFormatVA(dest, destSize, format, args);
va_end(args);
return result;
}
int32_t stringFormatVA(
char_t *dest,
const size_t destSize,
const char_t *format,
va_list args
) {
assertNotNull(dest, "dest must not be NULL");
assertNotNull(format, "format must not be NULL");
assertTrue(destSize > 0, "destSize must be greater than 0");
int32_t ret = vsnprintf(dest, destSize, format, args);
assertTrue(ret >= 0, "Failed to format string.");
assertTrue(ret < destSize, "Formatted string is too long.");
return ret;
}
bool_t stringToI32(const char_t *str, int32_t *out) {
assertNotNull(str, "str must not be NULL");
assertNotNull(out, "out must not be NULL");
char_t *endptr;
errno = 0;
long int result = strtol(str, &endptr, 10);
if (errno != 0 || *endptr != '\0') {
return false;
}
*out = (int32_t)result;
return true;
}
bool_t stringToI64(const char_t *str, int64_t *out) {
assertNotNull(str, "str must not be NULL");
assertNotNull(out, "out must not be NULL");
char_t *endptr;
errno = 0;
long long int result = strtoll(str, &endptr, 10);
if (errno != 0 || *endptr != '\0') {
return false;
}
*out = (int64_t)result;
return true;
}
bool_t stringToU16(const char_t *str, uint16_t *out) {
assertNotNull(str, "str must not be NULL");
assertNotNull(out, "out must not be NULL");
char_t *endptr;
errno = 0;
unsigned long int result = strtoul(str, &endptr, 10);
if (errno != 0 || *endptr != '\0' || result > UINT16_MAX) {
return false;
}
*out = (uint16_t)result;
return true;
}

109
src/dusk/util/string.h Normal file
View File

@@ -0,0 +1,109 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
/**
* Determines if a character is whitespace.
*
* @param c The character to check.
* @return TRUE if the character is whitespace, FALSE otherwise.
*/
bool_t stringIsWhitespace(const char_t c);
/**
* Copies a string from src to dest, ensuring the dest string is null-terminated
* and does not exceed the specified size.
*
* @param dest The destination string.
* @param src The source string.
* @param destSize The size of the destination string exc. null terminator.
*/
void stringCopy(char_t *dest, const char_t *src, const size_t destSize);
/**
* Compares two strings.
*
* @param str1 The first string.
* @param str2 The second string.
* @return 0 if the strings are equal, -1 if str1 is less than str2, 1 if str1
* is greater than str2.
*/
int stringCompare(const char_t *str1, const char_t *str2);
/**
* Trims whitespace from the beginning and end of a string.
*
* @param str The string to trim.
*/
void stringTrim(char_t *str);
/**
* Gets the next token in a string using a delimiter.
* e.g. input: "Hello, World, Happy Monday!" with stringToken(input, ",") will
* return "Hello" then " World" then " Happy Monday!" on each subsequent call.
*
* @param str The string to split.
* @param delim The delimiter to split by.
* @return A pointer to the next token in the string.
*/
char_t * stringToken(char_t *str, const char_t *delim);
/**
* Formats a string.
*
* @param dest The destination string.
* @param destSize The size of the destination string exc. null terminator.
* @param format The format string.
* @param ... The arguments to format.
* @return The number of characters written.
*/
int32_t stringFormat(char_t *dest, const size_t destSize, char_t *format, ...);
/**
* Formats a string using a va_list.
*
* @param dest The destination string.
* @param destSize The size of the destination string exc. null terminator.
* @param format The format string.
* @param args The va_list of arguments.
* @return The number of characters written.
*/
int32_t stringFormatVA(
char_t *dest,
const size_t destSize,
const char_t *format,
va_list args
);
/**
* Converts a string to an integer.
*
* @param str The string to convert.
* @param out The output integer.
* @return TRUE if the conversion was successful, FALSE otherwise.
*/
bool_t stringToI32(const char_t *str, int32_t *out);
/**
* Converts a string to a signed 16-bit integer.
*
* @param str The string to convert.
* @param out The output signed integer.
* @return TRUE if the conversion was successful, FALSE otherwise.
*/
bool_t stringToI16(const char_t *str, int16_t *out);
/**
* Converts a string to an unsigned 16-bit integer.
*
* @param str The string to convert.
* @param out The output unsigned integer.
* @return TRUE if the conversion was successful, FALSE otherwise.
*/
bool_t stringToU16(const char_t *str, uint16_t *out);

View File

@@ -0,0 +1,29 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Libs
find_package(pspsdk REQUIRED)
target_link_libraries(${DUSK_TARGET_NAME}
PRIVATE
pspsdk
)
# Includes
target_include_directories(${DUSK_TARGET_NAME}
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
input.c
main.c
duskpsp.c
)
# Subdirs
add_subdirectory(display)

View File

@@ -0,0 +1,12 @@
# 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
render.c
)
# Subdirs

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "render.h"
#include "input.h"
void renderInit(void) {
pspDebugScreenInit();
}
void renderDraw(void) {
sceDisplayWaitVblankStart();
pspDebugScreenSetXY(0, 2);
pspDebugScreenPrintf("Checking buttons \n");
if(inputIsDown(INPUT_BIND_UP)) {
pspDebugScreenPrintf("Up pressed\n");
}
if(inputIsDown(INPUT_BIND_DOWN)) {
pspDebugScreenPrintf("Down pressed\n");
}
if(inputIsDown(INPUT_BIND_LEFT)) {
pspDebugScreenPrintf("Left pressed\n");
}
if(inputIsDown(INPUT_BIND_RIGHT)) {
pspDebugScreenPrintf("Right pressed\n");
}
if(inputIsDown(INPUT_BIND_ACTION)) {
pspDebugScreenPrintf("Action pressed\n");
}
if(inputIsDown(INPUT_BIND_CANCEL)) {
pspDebugScreenPrintf("Cancel pressed\n");
}
}
void renderDispose(void) {
}

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskpsp.h"
#include "display/renderbase.h"

11
src/duskpsp/duskpsp.c Normal file
View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "duskpsp.h"
PSP_MODULE_INFO("Dusk", 0, 1, 0);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER | THREAD_ATTR_VFPU);

13
src/duskpsp/duskpsp.h Normal file
View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include <pspkernel.h>
#include <pspdebug.h>
#include <pspdisplay.h>
#include <pspctrl.h>

27
src/duskpsp/input.c Normal file
View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "duskpsp.h"
#include "input.h"
uint8_t inputStateGet() {
SceCtrlData pad;
sceCtrlReadBufferPositive(&pad, 1);
if(pad.Buttons == 0) return 0;
uint8_t state = 0;
if(pad.Buttons & PSP_CTRL_UP) state |= INPUT_BIND_UP;
if(pad.Buttons & PSP_CTRL_DOWN) state |= INPUT_BIND_DOWN;
if(pad.Buttons & PSP_CTRL_LEFT) state |= INPUT_BIND_LEFT;
if(pad.Buttons & PSP_CTRL_RIGHT) state |= INPUT_BIND_RIGHT;
if(pad.Buttons & PSP_CTRL_CROSS) state |= INPUT_BIND_ACTION;
if(pad.Buttons & PSP_CTRL_CIRCLE) state |= INPUT_BIND_CANCEL;
return state;
}

46
src/duskpsp/main.c Normal file
View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/render.h"
#include "game.h"
int_t exit_callback(int arg1, int arg2, void *common) {
sceKernelExitGame();
return 0;
}
int_t callback_thread(SceSize args, void *argp) {
int cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
sceKernelRegisterExitCallback(cbid);
sceKernelSleepThreadCB();
return 0;
}
int_t setup_callbacks(void) {
int thid = sceKernelCreateThread("update_thread", callback_thread, 0x11, 0xFA0, 0, 0);
if(thid >= 0) {
sceKernelStartThread(thid, 0, 0);
}
return thid;
}
void main(void) {
setup_callbacks();
renderInit();
gameInit();
sceCtrlSetSamplingCycle(0);
sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);
while(true) {
gameUpdate();
renderDraw();
}
gameDispose();
renderDispose();
}

View File

@@ -3,15 +3,8 @@
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include(FetchContent)
# Libs
FetchContent_Declare(
raylib
URL https://github.com/raysan5/raylib/archive/refs/tags/5.5.tar.gz
URL_HASH MD5=61638c4c2c097fbca1d6a71e4da36c16
)
FetchContent_MakeAvailable(raylib)
find_package(raylib REQUIRED)
target_link_libraries(${DUSK_TARGET_NAME}
PUBLIC

View File

@@ -87,7 +87,3 @@ void renderDispose(void) {
UnloadTexture(RENDER_ENTITIES_TEXTURE);
CloseWindow();
}
bool_t renderShouldExit() {
return WindowShouldClose();
}

21
src/duskraylib/main.c Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/render.h"
#include "game.h"
void main(void) {
gameInit();
while(!WindowShouldClose()) {
gameUpdate();
renderDraw();
}
gameDispose();
renderDispose();
}