diff --git a/CMakeLists.txt b/CMakeLists.txt index 5284318..261b240 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 @@ -54,4 +61,16 @@ add_subdirectory(src) target_include_directories(${DUSK_TARGET_NAME} PUBLIC ${DUSK_GENERATED_HEADERS_DIR} -) \ No newline at end of file +) + +# 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() \ No newline at end of file diff --git a/cmake/modules/Findpspsdk.cmake b/cmake/modules/Findpspsdk.cmake new file mode 100644 index 0000000..d1602dd --- /dev/null +++ b/cmake/modules/Findpspsdk.cmake @@ -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() \ No newline at end of file diff --git a/cmake/modules/Findraylib.cmake b/cmake/modules/Findraylib.cmake new file mode 100644 index 0000000..a036178 --- /dev/null +++ b/cmake/modules/Findraylib.cmake @@ -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() \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a699daf..0f33d50 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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() \ No newline at end of file diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt index 9fc6bf7..9995218 100644 --- a/src/dusk/CMakeLists.txt +++ b/src/dusk/CMakeLists.txt @@ -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) diff --git a/src/dusk/console/CMakeLists.txt b/src/dusk/console/CMakeLists.txt new file mode 100644 index 0000000..c4da6c5 --- /dev/null +++ b/src/dusk/console/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/src/dusk/console/console.c b/src/dusk/console/console.c new file mode 100644 index 0000000..d43df5d --- /dev/null +++ b/src/dusk/console/console.c @@ -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 + ); +} \ No newline at end of file diff --git a/src/dusk/console/console.h b/src/dusk/console/console.h new file mode 100644 index 0000000..59ee0e8 --- /dev/null +++ b/src/dusk/console/console.h @@ -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); \ No newline at end of file diff --git a/src/dusk/console/consolecmd.c b/src/dusk/console/consolecmd.c new file mode 100644 index 0000000..f2923b9 --- /dev/null +++ b/src/dusk/console/consolecmd.c @@ -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; +} \ No newline at end of file diff --git a/src/dusk/console/consolecmd.h b/src/dusk/console/consolecmd.h new file mode 100644 index 0000000..09a3c4f --- /dev/null +++ b/src/dusk/console/consolecmd.h @@ -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 +); \ No newline at end of file diff --git a/src/dusk/console/consoledefs.h b/src/dusk/console/consoledefs.h new file mode 100644 index 0000000..6e6df0d --- /dev/null +++ b/src/dusk/console/consoledefs.h @@ -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 \ No newline at end of file diff --git a/src/dusk/console/consolevar.c b/src/dusk/console/consolevar.c new file mode 100644 index 0000000..e126c89 --- /dev/null +++ b/src/dusk/console/consolevar.c @@ -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; +} + diff --git a/src/dusk/console/consolevar.h b/src/dusk/console/consolevar.h new file mode 100644 index 0000000..af6a9df --- /dev/null +++ b/src/dusk/console/consolevar.h @@ -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); \ No newline at end of file diff --git a/src/dusk/display/renderbase.h b/src/dusk/display/renderbase.h index cb227ff..e753412 100644 --- a/src/dusk/display/renderbase.h +++ b/src/dusk/display/renderbase.h @@ -26,11 +26,4 @@ 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(); \ No newline at end of file +void renderDispose(void); \ No newline at end of file diff --git a/src/dusk/main.c b/src/dusk/game.c similarity index 53% rename from src/dusk/main.c rename to src/dusk/game.c index 8cf791d..0c49948 100644 --- a/src/dusk/main.c +++ b/src/dusk/game.c @@ -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(); - - overworldUpdate(); - uiTextboxUpdate(); - eventUpdate(); +void gameUpdate(void) { + overworldUpdate(); + uiTextboxUpdate(); + eventUpdate(); + + inputUpdate(); +} + +void gameDispose(void) { - // Update input for next frame. - inputUpdate(); - } - - renderDispose(); } \ No newline at end of file diff --git a/src/dusk/game.h b/src/dusk/game.h new file mode 100644 index 0000000..e117ea9 --- /dev/null +++ b/src/dusk/game.h @@ -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); \ No newline at end of file diff --git a/src/dusk/util/CMakeLists.txt b/src/dusk/util/CMakeLists.txt index acbc06d..24877e0 100644 --- a/src/dusk/util/CMakeLists.txt +++ b/src/dusk/util/CMakeLists.txt @@ -8,4 +8,5 @@ target_sources(${DUSK_TARGET_NAME} PRIVATE fixed.c memory.c + string.c ) \ No newline at end of file diff --git a/src/dusk/util/string.c b/src/dusk/util/string.c new file mode 100644 index 0000000..8bbc6ea --- /dev/null +++ b/src/dusk/util/string.c @@ -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; +} \ No newline at end of file diff --git a/src/dusk/util/string.h b/src/dusk/util/string.h new file mode 100644 index 0000000..1028ca3 --- /dev/null +++ b/src/dusk/util/string.h @@ -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); \ No newline at end of file diff --git a/src/duskpsp/CMakeLists.txt b/src/duskpsp/CMakeLists.txt new file mode 100644 index 0000000..89a17b9 --- /dev/null +++ b/src/duskpsp/CMakeLists.txt @@ -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) diff --git a/src/duskpsp/display/CMakeLists.txt b/src/duskpsp/display/CMakeLists.txt new file mode 100644 index 0000000..2d30a6a --- /dev/null +++ b/src/duskpsp/display/CMakeLists.txt @@ -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 \ No newline at end of file diff --git a/src/duskpsp/display/render.c b/src/duskpsp/display/render.c new file mode 100644 index 0000000..02983ac --- /dev/null +++ b/src/duskpsp/display/render.c @@ -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) { + +} \ No newline at end of file diff --git a/src/duskpsp/display/render.h b/src/duskpsp/display/render.h new file mode 100644 index 0000000..28e0c6f --- /dev/null +++ b/src/duskpsp/display/render.h @@ -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" diff --git a/src/duskpsp/duskpsp.c b/src/duskpsp/duskpsp.c new file mode 100644 index 0000000..b904f3b --- /dev/null +++ b/src/duskpsp/duskpsp.c @@ -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); \ No newline at end of file diff --git a/src/duskpsp/duskpsp.h b/src/duskpsp/duskpsp.h new file mode 100644 index 0000000..c60c3d0 --- /dev/null +++ b/src/duskpsp/duskpsp.h @@ -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 +#include +#include +#include \ No newline at end of file diff --git a/src/duskpsp/input.c b/src/duskpsp/input.c new file mode 100644 index 0000000..dc46821 --- /dev/null +++ b/src/duskpsp/input.c @@ -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; +} \ No newline at end of file diff --git a/src/duskpsp/main.c b/src/duskpsp/main.c new file mode 100644 index 0000000..822cd3e --- /dev/null +++ b/src/duskpsp/main.c @@ -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(); +} \ No newline at end of file diff --git a/src/duskraylib/CMakeLists.txt b/src/duskraylib/CMakeLists.txt index 0dbe76c..ff85a44 100644 --- a/src/duskraylib/CMakeLists.txt +++ b/src/duskraylib/CMakeLists.txt @@ -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 diff --git a/src/duskraylib/display/render.c b/src/duskraylib/display/render.c index 07449be..31e1306 100644 --- a/src/duskraylib/display/render.c +++ b/src/duskraylib/display/render.c @@ -86,8 +86,4 @@ void renderDispose(void) { UnloadTexture(RENDER_TILEMAP_TEXTURE); UnloadTexture(RENDER_ENTITIES_TEXTURE); CloseWindow(); -} - -bool_t renderShouldExit() { - return WindowShouldClose(); } \ No newline at end of file diff --git a/src/duskraylib/main.c b/src/duskraylib/main.c new file mode 100644 index 0000000..f60b3ac --- /dev/null +++ b/src/duskraylib/main.c @@ -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(); +} \ No newline at end of file