diff --git a/assets/init.js b/assets/init.js new file mode 100644 index 00000000..52b524ff --- /dev/null +++ b/assets/init.js @@ -0,0 +1,12 @@ +Console.print('This is called from JavaScript'); + +const platformNames = { + [System.PLATFORM_LINUX]: 'Linux', + [System.PLATFORM_KNULLI]: 'Knulli', + [System.PLATFORM_PSP]: 'PSP', + [System.PLATFORM_GAMECUBE]: 'GameCube', + [System.PLATFORM_WII]: 'Wii', +}; + +const platformName = platformNames[System.platform] || 'Unknown'; +Console.print('Platform: ' + platformName); diff --git a/cmake/modules/Findjerryscript.cmake b/cmake/modules/Findjerryscript.cmake new file mode 100644 index 00000000..c1f8161f --- /dev/null +++ b/cmake/modules/Findjerryscript.cmake @@ -0,0 +1,96 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Turn things off we don't need +set(JERRY_CMDLINE OFF CACHE BOOL "" FORCE) +set(JERRY_EXT ON CACHE BOOL "" FORCE) +set(JERRY_DEBUGGER OFF CACHE BOOL "" FORCE) +set(JERRY_BUILTIN_DATE OFF CACHE BOOL "" FORCE) +set(ENABLE_LTO OFF CACHE BOOL "" FORCE) + +# Fetch Jerry +include(FetchContent) +FetchContent_Declare( + jerryscript + GIT_REPOSITORY https://git.wish.moe/YourWishes/jerryscript + GIT_TAG float32-fix +) +FetchContent_MakeAvailable(jerryscript) + +# Mark found +set(jerryscript_FOUND ON) + +# Define targets +if(TARGET jerryscript-core) + set(JERRY_CORE_TARGET jerryscript-core) +elseif(TARGET jerry-core) + set(JERRY_CORE_TARGET jerry-core) +endif() + +if(TARGET jerryscript-ext) + set(JERRY_EXT_TARGET jerryscript-ext) +elseif(TARGET jerry-ext) + set(JERRY_EXT_TARGET jerry-ext) +endif() + +if(TARGET jerryscript-port-default) + set(JERRY_PORT_TARGET jerryscript-port-default) +elseif(TARGET jerry-port-default) + set(JERRY_PORT_TARGET jerry-port-default) +elseif(TARGET jerryscript-port) + set(JERRY_PORT_TARGET jerryscript-port) +elseif(TARGET jerry-port) + set(JERRY_PORT_TARGET jerry-port) +endif() + +if(NOT JERRY_CORE_TARGET) + message(FATAL_ERROR "JerryScript core target not found") +endif() + +if(NOT JERRY_EXT_TARGET) + message(FATAL_ERROR "JerryScript ext target not found") +endif() + +if(NOT JERRY_PORT_TARGET) + message(FATAL_ERROR "JerryScript port target not found") +endif() + +foreach(tgt IN ITEMS + ${JERRY_CORE_TARGET} + ${JERRY_EXT_TARGET} + ${JERRY_PORT_TARGET} +) + if(TARGET ${tgt}) + set_property(TARGET ${tgt} PROPERTY INTERPROCEDURAL_OPTIMIZATION OFF) + target_compile_definitions(${JERRY_CORE_TARGET} PRIVATE + JERRY_NUMBER_TYPE_FLOAT64=0 + JERRY_BUILTIN_DATE=0 + ) + endif() +endforeach() + +# Export include dirs through the targets +target_include_directories(${JERRY_CORE_TARGET} INTERFACE + ${jerryscript_SOURCE_DIR}/jerry-core/include +) + +target_include_directories(${JERRY_EXT_TARGET} INTERFACE + ${jerryscript_SOURCE_DIR}/jerry-ext/include +) + +target_include_directories(${JERRY_PORT_TARGET} INTERFACE + ${jerryscript_SOURCE_DIR}/jerry-port/default/include +) + +# Suppress JerryScript-only warning +if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${JERRY_CORE_TARGET} PRIVATE + -Wno-error + ) +endif() + +add_library(jerryscript::core ALIAS ${JERRY_CORE_TARGET}) +add_library(jerryscript::ext ALIAS ${JERRY_EXT_TARGET}) +add_library(jerryscript::port ALIAS ${JERRY_PORT_TARGET}) diff --git a/cmake/targets/knulli.cmake b/cmake/targets/knulli.cmake index 0f11fa85..140aecae 100644 --- a/cmake/targets/knulli.cmake +++ b/cmake/targets/knulli.cmake @@ -34,6 +34,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC DUSK_OPENGL DUSK_OPENGL_ES DUSK_LINUX + DUSK_KNULLI DUSK_DISPLAY_SIZE_DYNAMIC DUSK_DISPLAY_WIDTH_DEFAULT=640 DUSK_DISPLAY_HEIGHT_DEFAULT=480 diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt index d1a5e7c6..141f7256 100644 --- a/src/dusk/CMakeLists.txt +++ b/src/dusk/CMakeLists.txt @@ -32,6 +32,15 @@ if(NOT yyjson_FOUND) endif() endif() +if(NOT jerryscript_FOUND) + find_package(jerryscript REQUIRED) + target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC + jerryscript::core + jerryscript::ext + jerryscript::port + ) +endif() + if(DUSK_BACKTRACE) target_link_options(${DUSK_LIBRARY_TARGET_NAME} PUBLIC -rdynamic) target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC @@ -69,6 +78,7 @@ add_subdirectory(input) add_subdirectory(locale) add_subdirectory(physics) add_subdirectory(scene) +add_subdirectory(script) add_subdirectory(system) add_subdirectory(time) add_subdirectory(ui) diff --git a/src/dusk/asset/loader/CMakeLists.txt b/src/dusk/asset/loader/CMakeLists.txt index 9bca3019..21f5928c 100644 --- a/src/dusk/asset/loader/CMakeLists.txt +++ b/src/dusk/asset/loader/CMakeLists.txt @@ -14,4 +14,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} # Subdirs add_subdirectory(display) add_subdirectory(locale) -add_subdirectory(json) \ No newline at end of file +add_subdirectory(json) +add_subdirectory(script) \ No newline at end of file diff --git a/src/dusk/asset/loader/assetloader.c b/src/dusk/asset/loader/assetloader.c index 5987246f..bce09b0e 100644 --- a/src/dusk/asset/loader/assetloader.c +++ b/src/dusk/asset/loader/assetloader.c @@ -39,4 +39,10 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = { .loadAsync = assetJsonLoaderAsync, .dispose = assetJsonDispose }, + + [ASSET_LOADER_TYPE_SCRIPT] = { + .loadSync = assetScriptLoaderSync, + .loadAsync = assetScriptLoaderAsync, + .dispose = assetScriptDispose + }, }; diff --git a/src/dusk/asset/loader/assetloader.h b/src/dusk/asset/loader/assetloader.h index 8324581d..14e983bb 100644 --- a/src/dusk/asset/loader/assetloader.h +++ b/src/dusk/asset/loader/assetloader.h @@ -11,6 +11,7 @@ #include "asset/loader/display/assettilesetloader.h" #include "asset/loader/locale/assetlocaleloader.h" #include "asset/loader/json/assetjsonloader.h" +#include "asset/loader/script/assetscriptloader.h" typedef enum { ASSET_LOADER_TYPE_NULL, @@ -20,6 +21,7 @@ typedef enum { ASSET_LOADER_TYPE_TILESET, ASSET_LOADER_TYPE_LOCALE, ASSET_LOADER_TYPE_JSON, + ASSET_LOADER_TYPE_SCRIPT, ASSET_LOADER_TYPE_COUNT } assetloadertype_t; @@ -30,6 +32,7 @@ typedef union { assettilesetloaderinput_t tileset; assetlocaleloaderinput_t locale; assetjsonloaderinput_t json; + assetscriptloaderinput_t script; } assetloaderinput_t; typedef union { @@ -38,6 +41,7 @@ typedef union { assettilesetloaderloading_t tileset; assetlocaleloaderloading_t locale; assetjsonloaderloading_t json; + assetscriptloaderloading_t script; } assetloaderloading_t; typedef union { @@ -46,6 +50,7 @@ typedef union { assettilesetoutput_t tileset; assetlocaleoutput_t locale; assetjsonoutput_t json; + assetscriptoutput_t script; } assetloaderoutput_t; typedef struct assetloading_s assetloading_t; diff --git a/src/dusk/asset/loader/script/CMakeLists.txt b/src/dusk/asset/loader/script/CMakeLists.txt new file mode 100644 index 00000000..76d9bd92 --- /dev/null +++ b/src/dusk/asset/loader/script/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + assetscriptloader.c +) diff --git a/src/dusk/asset/loader/script/assetscriptloader.c b/src/dusk/asset/loader/script/assetscriptloader.c new file mode 100644 index 00000000..446d2801 --- /dev/null +++ b/src/dusk/asset/loader/script/assetscriptloader.c @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "assetscriptloader.h" +#include "asset/loader/assetloading.h" +#include "asset/loader/assetentry.h" +#include "asset/loader/assetloader.h" +#include "util/memory.h" +#include "assert/assert.h" +#include + +errorret_t assetScriptLoaderAsync(assetloading_t *loading) { + assertNotNull(loading, "Loading cannot be NULL"); + + if(loading->loading.script.state != ASSET_SCRIPT_LOADING_STATE_READ_FILE) { + errorOk(); + } + + assertNull(loading->loading.script.buffer, "Buffer already defined?"); + + assetfile_t *file = &loading->loading.script.file; + assetLoaderErrorChain(loading, assetFileInit(file, loading->entry->name, NULL, NULL)); + assetLoaderErrorChain(loading, assetFileOpen(file)); + + size_t capacity = ASSET_SCRIPT_CHUNK_SIZE; + uint8_t *buffer = memoryAllocate(capacity + 1); + size_t offset = 0; + + while(1) { + if(offset + ASSET_SCRIPT_CHUNK_SIZE > capacity) { + size_t oldCapacity = capacity + 1; + capacity += ASSET_SCRIPT_CHUNK_SIZE; + memoryResize((void **)&buffer, oldCapacity, capacity + 1); + } + assetLoaderErrorChain(loading, assetFileRead( + file, buffer + offset, ASSET_SCRIPT_CHUNK_SIZE + )); + size_t chunk = (size_t)file->lastRead; + offset += chunk; + if(chunk == 0) break; + } + + buffer[offset] = '\0'; + assetLoaderErrorChain(loading, assetFileClose(file)); + assetLoaderErrorChain(loading, assetFileDispose(file)); + + loading->loading.script.buffer = buffer; + loading->loading.script.size = offset; + loading->loading.script.state = ASSET_SCRIPT_LOADING_STATE_EXEC; + loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; + errorOk(); +} + +errorret_t assetScriptLoaderSync(assetloading_t *loading) { + assertNotNull(loading, "Loading cannot be NULL"); + assertTrue(loading->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type."); + + switch(loading->loading.script.state) { + case ASSET_SCRIPT_LOADING_STATE_INITIAL: + loading->loading.script.state = ASSET_SCRIPT_LOADING_STATE_READ_FILE; + loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC; + errorOk(); + break; + + case ASSET_SCRIPT_LOADING_STATE_EXEC: + break; + + default: + errorOk(); + } + + uint8_t *buffer = loading->loading.script.buffer; + assertNotNull(buffer, "Script buffer should have been loaded by now."); + + jerry_value_t result = jerry_eval( + (const jerry_char_t *)buffer, + loading->loading.script.size, + JERRY_PARSE_NO_OPTS + ); + memoryFree(buffer); + loading->loading.script.buffer = NULL; + + if(jerry_value_is_exception(result)) { + jerry_value_t errVal = jerry_exception_value(result, false); + jerry_value_t errStr = jerry_value_to_string(errVal); + char_t buf[256]; + jerry_size_t len = jerry_string_to_buffer( + errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1 + ); + buf[len] = '\0'; + jerry_value_free(errStr); + jerry_value_free(errVal); + jerry_value_free(result); + assetLoaderErrorThrow(loading, "Script error in '%s': %s", + loading->entry->name, buf + ); + } + + loading->entry->data.script = (assetscriptoutput_t)result; + loading->entry->state = ASSET_ENTRY_STATE_LOADED; + errorOk(); +} + +errorret_t assetScriptDispose(assetentry_t *entry) { + assertNotNull(entry, "Asset entry cannot be NULL"); + assertTrue(entry->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type."); + if(entry->data.script != 0) { + jerry_value_free((jerry_value_t)entry->data.script); + entry->data.script = 0; + } + errorOk(); +} diff --git a/src/dusk/asset/loader/script/assetscriptloader.h b/src/dusk/asset/loader/script/assetscriptloader.h new file mode 100644 index 00000000..ee006659 --- /dev/null +++ b/src/dusk/asset/loader/script/assetscriptloader.h @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "asset/assetfile.h" + +#define ASSET_SCRIPT_CHUNK_SIZE 1024 + +typedef struct assetloading_s assetloading_t; +typedef struct assetentry_s assetentry_t; + +typedef struct { void *nothing; } assetscriptloaderinput_t; +typedef uint32_t assetscriptoutput_t; + +typedef enum { + ASSET_SCRIPT_LOADING_STATE_INITIAL, + ASSET_SCRIPT_LOADING_STATE_READ_FILE, + ASSET_SCRIPT_LOADING_STATE_EXEC, + ASSET_SCRIPT_LOADING_STATE_DONE +} assetscriptloadingstate_t; + +typedef struct { + assetfile_t file; + assetscriptloadingstate_t state; + uint8_t *buffer; + size_t size; +} assetscriptloaderloading_t; + +errorret_t assetScriptLoaderAsync(assetloading_t *loading); +errorret_t assetScriptLoaderSync(assetloading_t *loading); +errorret_t assetScriptDispose(assetentry_t *entry); diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 797fdcec..ecf91c8f 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -23,6 +23,7 @@ #include "network/network.h" #include "system/system.h" #include "console/console.h" +#include "script/script.h" #include "item/backpack.h" #include "save/save.h" @@ -54,9 +55,10 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { physicsManagerInit(); errorChain(networkInit()); - /* Run the init script. */ + errorChain(scriptInit()); + errorChain(scriptExecFile("init.js")); + consolePrint("Engine initialized"); - sceneSet(SCENE_TYPE_OVERWORLD); errorOk(); } @@ -96,6 +98,7 @@ errorret_t engineDispose(void) { errorChain(displayDispose()); // errorChain(saveDispose()); errorChain(assetDispose()); + errorChain(scriptDispose()); errorOk(); } diff --git a/src/dusk/script/CMakeLists.txt b/src/dusk/script/CMakeLists.txt new file mode 100644 index 00000000..f9452d50 --- /dev/null +++ b/src/dusk/script/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + script.c + scriptproto.c +) + +# Subdirectories +add_subdirectory(module) \ No newline at end of file diff --git a/src/dusk/script/module/CMakeLists.txt b/src/dusk/script/module/CMakeLists.txt new file mode 100644 index 00000000..bbfc716d --- /dev/null +++ b/src/dusk/script/module/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulebase.c +) diff --git a/src/dusk/script/module/console/moduleconsole.h b/src/dusk/script/module/console/moduleconsole.h new file mode 100644 index 00000000..29f3b114 --- /dev/null +++ b/src/dusk/script/module/console/moduleconsole.h @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "console/console.h" + +static scriptproto_t MODULE_CONSOLE_PROTO; + +moduleBaseFunction(moduleConsolePrint) { + char_t buf[512]; + char_t msg[4096]; + size_t msgLen = 0; + + for(jerry_length_t i = 0; i < argc; ++i) { + jerry_value_t strVal = jerry_value_to_string(args[i]); + moduleBaseToString(strVal, buf, sizeof(buf)); + jerry_value_free(strVal); + + size_t partLen = strlen(buf); + if(msgLen + partLen + 1 < sizeof(msg)) { + stringCopy(msg + msgLen, buf, sizeof(msg) - msgLen); + msgLen += partLen; + } + + if(i + 1 < argc && msgLen + 1 < sizeof(msg)) { + msg[msgLen++] = '\t'; + msg[msgLen] = '\0'; + } + } + + consolePrint("%s", msg); + return jerry_undefined(); +} + +moduleBaseFunction(moduleConsoleGetVisible) { + return jerry_boolean(CONSOLE.visible); +} + +moduleBaseFunction(moduleConsoleSetVisible) { + moduleBaseRequireArgs(1); + CONSOLE.visible = moduleBaseArgBool(0); + return jerry_undefined(); +} + +static void moduleConsoleInit(void) { + scriptProtoInit( + &MODULE_CONSOLE_PROTO, "Console", + sizeof(uint8_t), NULL + ); + + scriptProtoDefineStaticFunc( + &MODULE_CONSOLE_PROTO, "print", moduleConsolePrint + ); + scriptProtoDefineStaticProp( + &MODULE_CONSOLE_PROTO, "visible", + moduleConsoleGetVisible, moduleConsoleSetVisible + ); +} + +static void moduleConsoleDispose(void) { + scriptProtoDispose(&MODULE_CONSOLE_PROTO); +} diff --git a/src/dusk/script/module/engine/moduleengine.h b/src/dusk/script/module/engine/moduleengine.h new file mode 100644 index 00000000..693ce978 --- /dev/null +++ b/src/dusk/script/module/engine/moduleengine.h @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "engine/engine.h" + +static scriptproto_t MODULE_ENGINE_PROTO; + +moduleBaseFunction(moduleEngineGetRunning) { + return jerry_boolean(ENGINE.running); +} + +moduleBaseFunction(moduleEngineExit) { + ENGINE.running = false; + return jerry_undefined(); +} + +static void moduleEngineInit(void) { + scriptProtoInit(&MODULE_ENGINE_PROTO, "Engine", sizeof(uint8_t), NULL); + + scriptProtoDefineStaticProp( + &MODULE_ENGINE_PROTO, "running", + moduleEngineGetRunning, NULL + ); + scriptProtoDefineStaticFunc( + &MODULE_ENGINE_PROTO, "exit", moduleEngineExit + ); +} + +static void moduleEngineDispose(void) { + scriptProtoDispose(&MODULE_ENGINE_PROTO); +} diff --git a/src/dusk/script/module/input/moduleinput.h b/src/dusk/script/module/input/moduleinput.h new file mode 100644 index 00000000..bc1e9080 --- /dev/null +++ b/src/dusk/script/module/input/moduleinput.h @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "input/input.h" +#include "input/inputbutton.h" + +static scriptproto_t MODULE_INPUT_PROTO; + +/** Validates an inputaction_t argument and returns a type error if bad. */ +#define moduleInputRequireAction(i, ctx) do { \ + if(!jerry_value_is_number(args[(i)])) { \ + return moduleBaseThrow(ctx ": action must be a number"); \ + } \ + const inputaction_t _act = (inputaction_t)moduleBaseArgInt(i); \ + if(_act <= INPUT_ACTION_NULL || _act >= INPUT_ACTION_COUNT) { \ + return moduleBaseThrow(ctx ": invalid action"); \ + } \ +} while(0) + +moduleBaseFunction(moduleInputIsDown) { + moduleInputRequireAction(0, "Input.isDown"); + return jerry_boolean( + inputIsDown((inputaction_t)moduleBaseArgInt(0)) + ); +} + +moduleBaseFunction(moduleInputWasDown) { + moduleInputRequireAction(0, "Input.wasDown"); + return jerry_boolean( + inputWasDown((inputaction_t)moduleBaseArgInt(0)) + ); +} + +moduleBaseFunction(moduleInputPressed) { + moduleInputRequireAction(0, "Input.pressed"); + return jerry_boolean( + inputPressed((inputaction_t)moduleBaseArgInt(0)) + ); +} + +moduleBaseFunction(moduleInputReleased) { + moduleInputRequireAction(0, "Input.released"); + return jerry_boolean( + inputReleased((inputaction_t)moduleBaseArgInt(0)) + ); +} + +moduleBaseFunction(moduleInputGetValue) { + moduleInputRequireAction(0, "Input.getValue"); + return jerry_number( + inputGetCurrentValue((inputaction_t)moduleBaseArgInt(0)) + ); +} + +moduleBaseFunction(moduleInputAxis) { + moduleInputRequireAction(0, "Input.axis"); + moduleInputRequireAction(1, "Input.axis"); + return jerry_number(inputAxis( + (inputaction_t)moduleBaseArgInt(0), + (inputaction_t)moduleBaseArgInt(1) + )); +} + +moduleBaseFunction(moduleInputBind) { + moduleBaseRequireArgs(2); + moduleBaseRequireString(0); + if(!jerry_value_is_number(args[1])) { + return moduleBaseThrow("Input.bind: action must be a number"); + } + const inputaction_t action = (inputaction_t)moduleBaseArgInt(1); + if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { + return moduleBaseThrow("Input.bind: invalid action"); + } + + char_t name[128]; + moduleBaseToString(args[0], name, sizeof(name)); + if(name[0] == '\0') { + return moduleBaseThrow("Input.bind: button name cannot be empty"); + } + + inputbutton_t btn = inputButtonGetByName(name); + if(btn.type == INPUT_BUTTON_TYPE_NONE) { + return moduleBaseThrow("Input.bind: unknown button name"); + } + + inputBind(btn, action); + return jerry_undefined(); +} + +static void moduleInputInit(void) { + scriptProtoInit(&MODULE_INPUT_PROTO, "Input", sizeof(uint8_t), NULL); + + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "isDown", moduleInputIsDown + ); + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "wasDown", moduleInputWasDown + ); + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "pressed", moduleInputPressed + ); + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "released", moduleInputReleased + ); + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "getValue", moduleInputGetValue + ); + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "axis", moduleInputAxis + ); + scriptProtoDefineStaticFunc( + &MODULE_INPUT_PROTO, "bind", moduleInputBind + ); + + /* Register INPUT_ACTION_* integer constants into the global scope. */ + jerry_value_t result = jerry_eval( + (const jerry_char_t *)INPUT_ACTION_SCRIPT, + strlen(INPUT_ACTION_SCRIPT), + JERRY_PARSE_NO_OPTS + ); + jerry_value_free(result); +} + +static void moduleInputDispose(void) { + scriptProtoDispose(&MODULE_INPUT_PROTO); +} diff --git a/src/dusk/script/module/modulebase.c b/src/dusk/script/module/modulebase.c new file mode 100644 index 00000000..c3797f6d --- /dev/null +++ b/src/dusk/script/module/modulebase.c @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulebase.h" +#include "util/memory.h" + +jerry_object_native_info_t MODULE_BASE_NATIVE_INFO = { + .free_cb = moduleBaseFreeProto, + .number_of_references = 0, + .offset_of_references = 0 +}; + +void moduleBaseFreeProto(void *ptr, jerry_object_native_info_t *info) { + assertNotNull(ptr, "Pointer must not be null"); + assertNotNull(info, "Native info must not be null"); + memoryFree(ptr); +} + +jerry_value_t moduleBaseThrow(const char_t *message) { + assertStrLenMin(message, 1, "Error message must not be empty"); + return jerry_throw_sz(JERRY_ERROR_TYPE, message); +} + +jerry_value_t moduleBaseThrowError(const errorret_t err) { + assertNotNull(err.state, "Error state must not be NULL"); + assertNotNull(err.state->message, "Error message must not be NULL"); + jerry_value_t jsErr = jerry_throw_sz(JERRY_ERROR_TYPE, err.state->message); + errorCatch(err); + return jsErr; +} + +void moduleBaseToString(jerry_value_t val, char_t *buf, jerry_size_t buflen) { + jerry_size_t len = jerry_string_to_buffer( + val, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, buflen - 1 + ); + buf[len] = '\0'; +} + +void moduleBaseExceptionMessage( + jerry_value_t exception, + char_t *buf, + size_t buflen +) { + jerry_value_t errVal = jerry_exception_value(exception, false); + jerry_value_t errStr = jerry_value_to_string(errVal); + jerry_size_t len = jerry_string_to_buffer( + errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, (jerry_size_t)(buflen - 1) + ); + buf[len] = '\0'; + jerry_value_free(errStr); + jerry_value_free(errVal); +} + +void moduleBaseDefineMethod( + jerry_value_t obj, + const char_t *name, + jerry_external_handler_t fn +) { + jerry_value_t key = jerry_string_sz(name); + jerry_value_t func = jerry_function_external(fn); + jerry_object_set(obj, key, func); + jerry_value_free(func); + jerry_value_free(key); +} + +void moduleBaseDefineGlobalMethod( + const char_t *name, + jerry_external_handler_t fn +) { + jerry_value_t global = jerry_current_realm(); + moduleBaseDefineMethod(global, name, fn); + jerry_value_free(global); +} + +void moduleBaseSetValue(const char_t *name, jerry_value_t value) { + jerry_value_t global = jerry_current_realm(); + jerry_value_t key = jerry_string_sz(name); + jerry_object_set(global, key, value); + jerry_value_free(key); + jerry_value_free(global); +} + +jerry_value_t moduleBaseWrapPointer(void *ptr) { + jerry_value_t obj = jerry_object(); + jerry_object_set_native_ptr(obj, &MODULE_BASE_NATIVE_INFO, ptr); + return obj; +} + +void *moduleBaseUnwrapPointer(jerry_value_t val) { + if(!jerry_value_is_object(val)) return NULL; + return jerry_object_get_native_ptr(val, &MODULE_BASE_NATIVE_INFO); +} + +jerry_value_t moduleBaseGetProp( + jerry_value_t obj, const char_t *name +) { + jerry_value_t key = jerry_string_sz(name); + jerry_value_t val = jerry_object_get(obj, key); + jerry_value_free(key); + return val; +} + +float_t moduleBaseValueFloat(jerry_value_t val) { + return (float_t)jerry_value_as_number(val); +} + +int32_t moduleBaseValueInt(jerry_value_t val) { + return (int32_t)jerry_value_as_number(val); +} + +void moduleBaseSetNumber(const char_t *name, double value) { + jerry_value_t global = jerry_current_realm(); + jerry_value_t key = jerry_string_sz(name); + jerry_value_t val = jerry_number(value); + jerry_object_set(global, key, val); + jerry_value_free(val); + jerry_value_free(key); + jerry_value_free(global); +} + +void moduleBaseSetInt(const char_t *name, int32_t value) { + moduleBaseSetNumber(name, (double)value); +} \ No newline at end of file diff --git a/src/dusk/script/module/modulebase.h b/src/dusk/script/module/modulebase.h new file mode 100644 index 00000000..f564d0fc --- /dev/null +++ b/src/dusk/script/module/modulebase.h @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "assert/assert.h" +#include "util/string.h" +#include +#include + +extern jerry_object_native_info_t MODULE_BASE_NATIVE_INFO; + +/** + * Frees a native pointer associated with a JavaScript object, uses the internal + * memory functions. + * + * @param ptr The native pointer to free. Must not be NULL. + * @param info The native info associated with the pointer. Must not be NULL. + */ +void moduleBaseFreeProto(void *ptr, jerry_object_native_info_t *info); + +/** + * Throws a JavaScript error with the given message. + * + * @param message The error message to throw. Must not be empty. + * @return A jerry_value_t representing the thrown error. + */ +jerry_value_t moduleBaseThrow(const char_t *message); + +/** + * "Converts" a dusk error to a jerry type error. + * + * @param err Native dusk error to convert. + * @return A jerry_value_t representing the thrown error. + */ +jerry_value_t moduleBaseThrowError(const errorret_t err); + +/** + * Converts a jerry value to a string and writes it to the provided buffer. + * + * @param val The jerry value to convert. + * @param buf The buffer to write the string to. + * @param buflen The length of the buffer. + */ +void moduleBaseToString(jerry_value_t val, char_t *buf, jerry_size_t buflen); + +/** + * Converts a jerry exception value to a string and writes it to the provided + * buffer. + * + * @param exception The jerry exception value to convert. + * @param buf The buffer to write the string to. + * @param buflen The length of the buffer. + */ +void moduleBaseExceptionMessage( + jerry_value_t exception, + char_t *buf, + size_t buflen +); + +/** + * Defines a method on a given object. + * + * @param obj The object to define the method on. + * @param name The name of the method. + * @param fn The function pointer to the method implementation. + */ +void moduleBaseDefineMethod( + jerry_value_t obj, + const char_t *name, + jerry_external_handler_t fn +); + +/** + * Defines a global method. + * + * @param name The name of the method. + * @param fn The function pointer to the method implementation. + */ +void moduleBaseDefineGlobalMethod( + const char_t *name, + jerry_external_handler_t fn +); + +/** + * Sets a global variable to a given jerry value. + * + * @param name The name of the global variable. + * @param value The jerry value to set the variable to. + */ +void moduleBaseSetValue(const char_t *name, jerry_value_t value); + +/** + * Wraps a native pointer in a jerry object. + * + * @param ptr The native pointer to wrap. + * @return A jerry_value_t representing the wrapped pointer. + */ +jerry_value_t moduleBaseWrapPointer(void *ptr); + +/** + * Unwraps a native pointer from a jerry object. + * + * @param val The jerry value to unwrap the pointer from. + * @return The unwrapped native pointer. + */ +void *moduleBaseUnwrapPointer(jerry_value_t val); + +/** + * Gets a property from a jerry object as a jerry value. + * + * @param obj The object to get the property from. + * @param name The name of the property to get. + * @return The value of the property. + */ +jerry_value_t moduleBaseGetProp( + jerry_value_t obj, const char_t *name +); + +/** + * Converts a jerry value to a float. + * + * @param val The jerry value to convert. + * @return The converted float value. + */ +float_t moduleBaseValueFloat(jerry_value_t val); + +/** + * Converts a jerry value to an int. + * + * @param val The jerry value to convert. + * @return The converted int value. + */ +int32_t moduleBaseValueInt(jerry_value_t val); + +/** + * Sets a global variable to a given number value. + * + * @param name The name of the global variable. + * @param value The number value to set the variable to. + */ +void moduleBaseSetNumber(const char_t *name, double value); + +/** + * Sets a global variable to a given int value. + * + * @param name The name of the global variable. + * @param value The int value to set the variable to. + */ +void moduleBaseSetInt(const char_t *name, int32_t value); + +#define moduleBaseFunction(name) \ + static jerry_value_t name( \ + const jerry_call_info_t *callInfo, \ + const jerry_value_t args[], \ + const jerry_length_t argc) + +#define moduleBaseRequireArgs(n) do { \ + if(argc < (jerry_length_t)(n)) { \ + return moduleBaseThrow("Expected at least " #n " argument(s)"); \ + } \ +} while(0) + +#define moduleBaseRequireNumber(i) do { \ + if(!jerry_value_is_number(args[(i)])) { \ + return moduleBaseThrow("Expected number argument"); \ + } \ +} while(0) + +#define moduleBaseRequireString(i) do { \ + if(!jerry_value_is_string(args[(i)])) { \ + return moduleBaseThrow("Expected string argument"); \ + } \ +} while(0) + +#define moduleBaseArgFloat(i) ((float_t)jerry_value_as_number(args[(i)])) +#define moduleBaseArgInt(i) ((int32_t)jerry_value_as_number(args[(i)])) +#define moduleBaseArgBool(i) (jerry_value_is_true(args[(i)])) + +#define moduleBaseOptFloat(i, def) \ + ((jerry_length_t)(i) < argc && jerry_value_is_number(args[(i)]) \ + ? (float_t)jerry_value_as_number(args[(i)]) : (def)) + +#define moduleBaseOptInt(i, def) \ + ((jerry_length_t)(i) < argc && jerry_value_is_number(args[(i)]) \ + ? (int32_t)jerry_value_as_number(args[(i)]) : (def)) diff --git a/src/dusk/script/module/modulelist.h b/src/dusk/script/module/modulelist.h new file mode 100644 index 00000000..4376276e --- /dev/null +++ b/src/dusk/script/module/modulelist.h @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/console/moduleconsole.h" +#include "script/module/engine/moduleengine.h" +#include "script/module/input/moduleinput.h" +#include "script/module/system/modulesystem.h" + +static void moduleListInit(void) { + moduleConsoleInit(); + moduleEngineInit(); + moduleInputInit(); + moduleSystemInit(); +} + +static void moduleListDispose(void) { + moduleSystemDispose(); + moduleInputDispose(); + moduleEngineDispose(); + moduleConsoleDispose(); +} diff --git a/src/dusk/script/module/system/modulesystem.h b/src/dusk/script/module/system/modulesystem.h new file mode 100644 index 00000000..8269af27 --- /dev/null +++ b/src/dusk/script/module/system/modulesystem.h @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "system/system.h" + +static scriptproto_t MODULE_SYSTEM_PROTO; + +moduleBaseFunction(moduleSystemGetPlatform) { + return jerry_number((double)systemGetPlatform()); +} + +static void moduleSystemInit(void) { + scriptProtoInit(&MODULE_SYSTEM_PROTO, "System", sizeof(uint8_t), NULL); + + scriptProtoDefineStaticProp( + &MODULE_SYSTEM_PROTO, "platform", + moduleSystemGetPlatform, NULL + ); + + /* Register PLATFORM_* integer constants on the System global. */ + jerry_value_t target = MODULE_SYSTEM_PROTO.prototype; + +#define SYSTEM_PLATFORM(name, value) \ + do { \ + jerry_value_t key = jerry_string_sz("PLATFORM_" #name); \ + jerry_value_t val = jerry_number((double)(value)); \ + jerry_object_set(target, key, val); \ + jerry_value_free(val); \ + jerry_value_free(key); \ + } while(0); + SYSTEM_PLATFORM_LIST +#undef SYSTEM_PLATFORM +} + +static void moduleSystemDispose(void) { + scriptProtoDispose(&MODULE_SYSTEM_PROTO); +} diff --git a/src/dusk/script/script.c b/src/dusk/script/script.c new file mode 100644 index 00000000..aa5799de --- /dev/null +++ b/src/dusk/script/script.c @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "script.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "asset/asset.h" +#include "asset/loader/assetloader.h" +#include "asset/loader/assetentry.h" +#include "script/module/modulelist.h" +#include + +script_t SCRIPT; + +errorret_t scriptInit(void) { + memoryZero(&SCRIPT, sizeof(script_t)); + + jerry_init(JERRY_INIT_EMPTY); + SCRIPT.initialized = true; + + moduleListInit(); + + errorOk(); +} + +errorret_t scriptExecString(const char_t *source) { + assertNotNull(source, "Source cannot be NULL"); + assertTrue(SCRIPT.initialized, "Script system not initialized"); + + jerry_value_t result = jerry_eval( + (const jerry_char_t *)source, + strlen(source), + JERRY_PARSE_NO_OPTS + ); + + if(jerry_value_is_exception(result)) { + char_t buf[256]; + jerry_value_t errVal = jerry_exception_value(result, false); + jerry_value_t errStr = jerry_value_to_string(errVal); + jerry_size_t len = jerry_string_to_buffer( + errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1 + ); + buf[len] = '\0'; + jerry_value_free(errStr); + jerry_value_free(errVal); + jerry_value_free(result); + errorThrow("Script error: %s", buf); + } + + jerry_value_free(result); + errorOk(); +} + +errorret_t scriptExecFile(const char_t *path) { + assertNotNull(path, "Path cannot be NULL"); + assertTrue(SCRIPT.initialized, "Script system not initialized"); + + assetentry_t *entry = assetGetEntry(path, ASSET_LOADER_TYPE_SCRIPT, NULL); + assertNotNull(entry, "Failed to get asset entry for script"); + errorChain(assetRequireLoaded(entry)); + errorOk(); +} + +errorret_t scriptDispose(void) { + if(!SCRIPT.initialized) errorOk(); + moduleListDispose(); + jerry_cleanup(); + SCRIPT.initialized = false; + errorOk(); +} diff --git a/src/dusk/script/script.h b/src/dusk/script/script.h new file mode 100644 index 00000000..3f0e8bd0 --- /dev/null +++ b/src/dusk/script/script.h @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +typedef struct { + bool_t initialized; +} script_t; + +extern script_t SCRIPT; + +/** + * Initializes the script system, starting up JerryScript and registering all + * built-in modules. + * + * @return Any error that occurred. + */ +errorret_t scriptInit(void); + +/** + * Evaluates a JS source string in the global scope. + * + * @param source Null-terminated JS source to evaluate. + * @return Any error that occurred. + */ +errorret_t scriptExecString(const char_t *source); + +/** + * Loads and evaluates a script asset from the archive. The result is cached + * by the asset system; repeated calls with the same path do not re-execute. + * + * @param path Path of the script inside the asset archive. + * @return Any error that occurred. + */ +errorret_t scriptExecFile(const char_t *path); + +/** + * Disposes of the script system and shuts down JerryScript. + * + * @return Any error that occurred. + */ +errorret_t scriptDispose(void); diff --git a/src/dusk/script/scriptproto.c b/src/dusk/script/scriptproto.c new file mode 100644 index 00000000..23ded8a4 --- /dev/null +++ b/src/dusk/script/scriptproto.c @@ -0,0 +1,186 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "scriptproto.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "script/module/modulebase.h" + +void scriptProtoInit( + scriptproto_t *proto, + const char_t *name, + const size_t size, + jerry_external_handler_t constructor +) { + assertNotNull(proto, "Script prototype struct must not be null"); + memoryZero(proto, sizeof(scriptproto_t)); + + proto->info = (jerry_object_native_info_t){ + .free_cb = moduleBaseFreeProto, + .number_of_references = 0, + .offset_of_references = 0 + }; + proto->prototype = jerry_object(); + proto->size = size; + + if(constructor != NULL) { + proto->constructor = jerry_function_external(constructor); + jerry_value_t protoKey = jerry_string_sz("prototype"); + jerry_object_set(proto->constructor, protoKey, proto->prototype); + jerry_value_free(protoKey); + jerry_value_t ctorKey = jerry_string_sz("constructor"); + jerry_object_set(proto->prototype, ctorKey, proto->constructor); + jerry_value_free(ctorKey); + } + + if(name != NULL) { + jerry_value_t global = jerry_current_realm(); + jerry_value_t key = jerry_string_sz(name); + jerry_value_t val = proto->constructor ? proto->constructor : proto->prototype; + jerry_object_set(global, key, val); + jerry_value_free(key); + jerry_value_free(global); + } +} + +void scriptProtoDefineProp( + scriptproto_t *proto, + const char_t *name, + jerry_external_handler_t getter, + jerry_external_handler_t setter +) { + assertNotNull(proto, "Script prototype struct must not be null"); + assertStrLenMin(name, 1, "Property name must not be empty"); + assertNotNull(getter, "Getter must not be null"); + + jerry_property_descriptor_t desc; + memoryZero(&desc, sizeof(desc)); + desc.flags = (uint16_t)( + JERRY_PROP_IS_GET_DEFINED | + JERRY_PROP_IS_ENUMERABLE_DEFINED | JERRY_PROP_IS_ENUMERABLE | + JERRY_PROP_IS_CONFIGURABLE_DEFINED | JERRY_PROP_IS_CONFIGURABLE + ); + desc.getter = jerry_function_external(getter); + if(setter != NULL) { + desc.flags |= JERRY_PROP_IS_SET_DEFINED; + desc.setter = jerry_function_external(setter); + } + + jerry_value_t key = jerry_string_sz(name); + jerry_value_t result = jerry_object_define_own_prop(proto->prototype, key, &desc); + jerry_value_free(result); + jerry_value_free(key); + jerry_value_free(desc.getter); + if(setter != NULL) jerry_value_free(desc.setter); +} + +void scriptProtoDefineFunc( + scriptproto_t *proto, + const char_t *name, + jerry_external_handler_t fn +) { + assertNotNull(proto, "Script prototype struct must not be null"); + assertStrLenMin(name, 1, "Method name must not be empty"); + assertNotNull(fn, "Function handler must not be null"); + + jerry_value_t key = jerry_string_sz(name); + jerry_value_t func = jerry_function_external(fn); + jerry_object_set(proto->prototype, key, func); + jerry_value_free(func); + jerry_value_free(key); +} + +void scriptProtoDefineStaticProp( + scriptproto_t *proto, + const char_t *name, + jerry_external_handler_t getter, + jerry_external_handler_t setter +) { + assertNotNull(proto, "Script prototype struct must not be null"); + assertStrLenMin(name, 1, "Property name must not be empty"); + assertNotNull(getter, "Getter must not be null"); + + jerry_value_t target = ( + proto->constructor ? proto->constructor : proto->prototype + ); + + jerry_property_descriptor_t desc; + memoryZero(&desc, sizeof(desc)); + desc.flags = (uint16_t)( + JERRY_PROP_IS_GET_DEFINED | + JERRY_PROP_IS_ENUMERABLE_DEFINED | JERRY_PROP_IS_ENUMERABLE | + JERRY_PROP_IS_CONFIGURABLE_DEFINED | JERRY_PROP_IS_CONFIGURABLE + ); + desc.getter = jerry_function_external(getter); + if(setter != NULL) { + desc.flags |= JERRY_PROP_IS_SET_DEFINED; + desc.setter = jerry_function_external(setter); + } + + jerry_value_t key = jerry_string_sz(name); + jerry_value_t result = jerry_object_define_own_prop(target, key, &desc); + jerry_value_free(result); + jerry_value_free(key); + jerry_value_free(desc.getter); + if(setter != NULL) jerry_value_free(desc.setter); +} + +void scriptProtoDefineStaticFunc( + scriptproto_t *proto, + const char_t *name, + jerry_external_handler_t fn +) { + assertNotNull(proto, "Script prototype struct must not be null"); + assertStrLenMin(name, 1, "Method name must not be empty"); + assertNotNull(fn, "Function handler must not be null"); + + jerry_value_t target = ( + proto->constructor ? proto->constructor : proto->prototype + ); + jerry_value_t key = jerry_string_sz(name); + jerry_value_t func = jerry_function_external(fn); + jerry_object_set(target, key, func); + jerry_value_free(func); + jerry_value_free(key); +} + +jerry_value_t scriptProtoCreateValue( + const scriptproto_t *proto, + const void *value +) { + assertNotNull(proto, "Script prototype struct must not be null"); + assertNotNull(value, "Value pointer must not be null"); + + void *ptr = memoryAllocate(proto->size); + memoryCopy(ptr, value, proto->size); + jerry_value_t obj = jerry_object(); + jerry_object_set_native_ptr(obj, &proto->info, ptr); + jerry_object_set_proto(obj, proto->prototype); + return obj; +} + +void *scriptProtoGetValue( + const scriptproto_t *proto, + const jerry_value_t obj +) { + assertNotNull(proto, "Script prototype struct must not be null"); + if(!jerry_value_is_object(obj)) return NULL; + return jerry_object_get_native_ptr(obj, &proto->info); +} + +void scriptProtoDefineToString( + scriptproto_t *proto, + jerry_external_handler_t fn +) { + scriptProtoDefineFunc(proto, "toString", fn); +} + +void scriptProtoDispose(scriptproto_t *proto) { + assertNotNull(proto, "Script prototype struct must not be null"); + jerry_value_free(proto->prototype); + if(proto->constructor) jerry_value_free(proto->constructor); +} diff --git a/src/dusk/script/scriptproto.h b/src/dusk/script/scriptproto.h new file mode 100644 index 00000000..eb11b24a --- /dev/null +++ b/src/dusk/script/scriptproto.h @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include + +/** + * Represents a JavaScript class prototype backed by a C struct. + * Use scriptProtoInit() to populate, scriptProtoDispose() to clean up. + */ +typedef struct { + /** Native-info tag used to identify instances of this class. */ + jerry_object_native_info_t info; + /** The JS prototype object shared by all instances. */ + jerry_value_t prototype; + /** The JS constructor function, or 0 if no constructor was given. */ + jerry_value_t constructor; + /** sizeof the C struct wrapped by each instance. */ + size_t size; +} scriptproto_t; + +/** + * Initializes a JS class prototype. + * + * If name is non-NULL the class is registered as a global. When ctor is + * also non-NULL the global is the constructor function (enabling + * `new Name(...)`); otherwise the prototype object itself becomes the + * global. + * + * @param proto The struct to initialize. + * @param name JS global name, or NULL to skip global registration. + * @param size sizeof the C struct this class wraps. + * @param ctor Constructor handler, or NULL for no constructor. + */ +void scriptProtoInit( + scriptproto_t *proto, + const char_t *name, + const size_t size, + jerry_external_handler_t ctor +); + +/** + * Defines an instance property with a getter and optional setter. + * + * @param proto The class prototype. + * @param name Property name. + * @param getter Getter handler (must not be NULL). + * @param setter Setter handler, or NULL for a read-only property. + */ +void scriptProtoDefineProp( + scriptproto_t *proto, + const char_t *name, + jerry_external_handler_t getter, + jerry_external_handler_t setter +); + +/** + * Defines an instance method on the class prototype. + * + * @param proto The class prototype. + * @param name Method name. + * @param fn C handler called when the method is invoked. + */ +void scriptProtoDefineFunc( + scriptproto_t *proto, + const char_t *name, + jerry_external_handler_t fn +); + +/** + * Defines a static property on the class (e.g. Console.visible). + * + * Attaches to the constructor when one exists, otherwise attaches + * directly to the prototype object (which is the global in that case). + * + * @param proto The class prototype. + * @param name Property name. + * @param getter Getter handler (must not be NULL). + * @param setter Setter handler, or NULL for a read-only property. + */ +void scriptProtoDefineStaticProp( + scriptproto_t *proto, + const char_t *name, + jerry_external_handler_t getter, + jerry_external_handler_t setter +); + +/** + * Defines a static method on the class (e.g. Console.print). + * + * Attaches to the constructor when one exists, otherwise attaches + * directly to the prototype object. + * + * @param proto The class prototype. + * @param name Method name. + * @param fn C handler called when the static method is invoked. + */ +void scriptProtoDefineStaticFunc( + scriptproto_t *proto, + const char_t *name, + jerry_external_handler_t fn +); + +/** + * Creates a JS instance wrapping a copy of a C value. + * + * Allocates a copy of the value, sets it as native data on a new JS + * object, and applies the class prototype. + * + * @param proto The class prototype. + * @param value Pointer to the C value to copy into the new JS object. + * @return A new JS object with the class prototype and native data set. + */ +jerry_value_t scriptProtoCreateValue( + const scriptproto_t *proto, + const void *value +); + +/** + * Unwraps the native C pointer from a JS object instance. + * + * @param proto The class prototype. + * @param obj The JS object to inspect. + * @return Pointer to the wrapped C value, or NULL if not an instance. + */ +void *scriptProtoGetValue( + const scriptproto_t *proto, + const jerry_value_t obj +); + +/** + * Defines a toString() method on the class prototype. + * + * @param proto The class prototype. + * @param fn C handler called when toString() is invoked on an + * instance. + */ +void scriptProtoDefineToString( + scriptproto_t *proto, + jerry_external_handler_t fn +); + +/** + * Releases all JerryScript resources held by the prototype. + * + * Must be called before jerry_cleanup() to avoid heap finalizer + * crashes from live jerry_value_t GC roots. + * + * @param proto The class prototype to dispose. + */ +void scriptProtoDispose(scriptproto_t *proto); diff --git a/src/dusk/system/system.c b/src/dusk/system/system.c index 7c872a43..e8c07d68 100644 --- a/src/dusk/system/system.c +++ b/src/dusk/system/system.c @@ -22,4 +22,18 @@ errorret_t systemInit() { systemdialogtype_t systemGetActiveDialogType() { return systemGetActiveDialogTypePlatform(); +} + +systemplatform_t systemGetPlatform(void) { + #if defined(DUSK_KNULLI) + return SYSTEM_PLATFORM_KNULLI; + #elif defined(DUSK_PSP) + return SYSTEM_PLATFORM_PSP; + #elif defined(DUSK_GAMECUBE) + return SYSTEM_PLATFORM_GAMECUBE; + #elif defined(DUSK_WII) + return SYSTEM_PLATFORM_WII; + #else + return SYSTEM_PLATFORM_LINUX; + #endif } \ No newline at end of file diff --git a/src/dusk/system/system.h b/src/dusk/system/system.h index e009c7b0..d819f943 100644 --- a/src/dusk/system/system.h +++ b/src/dusk/system/system.h @@ -7,6 +7,11 @@ #pragma once #include "error/error.h" +#include "system/systemplatformlist.h" + +#define SYSTEM_PLATFORM(name, value) SYSTEM_PLATFORM_##name = value, +typedef enum { SYSTEM_PLATFORM_LIST } systemplatform_t; +#undef SYSTEM_PLATFORM typedef enum { SYSTEM_DIALOG_TYPE_NONE, @@ -35,4 +40,11 @@ errorret_t systemInit(void); * * @return Dialog type currently open. */ -systemdialogtype_t systemGetActiveDialogType(); \ No newline at end of file +systemdialogtype_t systemGetActiveDialogType(); + +/** + * Returns the platform the engine is currently running on. + * + * @return The current platform. + */ +systemplatform_t systemGetPlatform(void); \ No newline at end of file diff --git a/src/dusk/system/systemplatformlist.h b/src/dusk/system/systemplatformlist.h new file mode 100644 index 00000000..af5f2f0c --- /dev/null +++ b/src/dusk/system/systemplatformlist.h @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * X-macro list of all supported platforms. + * Usage: SYSTEM_PLATFORM(name, value) + * + * Example: + * #define SYSTEM_PLATFORM(name, value) \ + * case SYSTEM_PLATFORM_##name: return #name; + * SYSTEM_PLATFORM_LIST + * #undef SYSTEM_PLATFORM + */ +#define SYSTEM_PLATFORM_LIST \ + SYSTEM_PLATFORM(LINUX, 0) \ + SYSTEM_PLATFORM(KNULLI, 1) \ + SYSTEM_PLATFORM(PSP, 2) \ + SYSTEM_PLATFORM(GAMECUBE, 3) \ + SYSTEM_PLATFORM(WII, 4)