diff --git a/assets/ui/test.js b/assets/ui/test.js new file mode 100644 index 00000000..4499a130 --- /dev/null +++ b/assets/ui/test.js @@ -0,0 +1,6 @@ +module = { + render() { + Text.draw(10, 10, "Hello World"); + SpriteBatch.flush(); + } +}; diff --git a/src/dusk/console/console.c b/src/dusk/console/console.c index a62ef2b6..16bb0883 100644 --- a/src/dusk/console/console.c +++ b/src/dusk/console/console.c @@ -136,7 +136,7 @@ errorret_t consoleDraw(void) { &FONT_TEXTURE_DEFAULT )); } - errorOk(); + return spriteBatchFlush(); } void consoleDispose(void) { diff --git a/src/dusk/display/spritebatch/spritebatch.h b/src/dusk/display/spritebatch/spritebatch.h index d7a4f878..48557db7 100644 --- a/src/dusk/display/spritebatch/spritebatch.h +++ b/src/dusk/display/spritebatch/spritebatch.h @@ -8,9 +8,9 @@ #pragma once #include "display/mesh/quad.h" -#define SPRITEBATCH_SPRITES_MAX 32 +#define SPRITEBATCH_SPRITES_MAX 256 #define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT) -#define SPRITEBATCH_FLUSH_COUNT 4 +#define SPRITEBATCH_FLUSH_COUNT 8 #define SPRITEBATCH_SPRITES_MAX_PER_FLUSH (\ SPRITEBATCH_SPRITES_MAX / SPRITEBATCH_FLUSH_COUNT \ ) diff --git a/src/dusk/item/CMakeLists.txt b/src/dusk/item/CMakeLists.txt new file mode 100644 index 00000000..025c5447 --- /dev/null +++ b/src/dusk/item/CMakeLists.txt @@ -0,0 +1,10 @@ +# 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 + inventory.c +) \ No newline at end of file diff --git a/src/dusk/item/inventory.c b/src/dusk/item/inventory.c new file mode 100644 index 00000000..41d6d3eb --- /dev/null +++ b/src/dusk/item/inventory.c @@ -0,0 +1,207 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "inventory.h" +#include "assert/assert.h" + +void inventoryInit( + inventory_t *inventory, + itemstack_t *inventoryItems, + const uint32_t inventorySize +) { + assertNotNull(inventory, "Inventory must not be null"); + assertNotNull(inventoryItems, "Inventory items must not be null"); + assertTrue(inventorySize > 0, "Inventory size must be greater than zero"); + + inventory->inventoryItems = inventoryItems; + inventory->inventorySize = inventorySize; + inventoryClear(inventory); +} + +uint8_t inventoryItemAdd( + inventory_t *inventory, + const itemtype_t type, + const uint8_t count +) { + assertNotNull(inventory, "Inventory must not be null"); + assertFalse(type == ITEM_TYPE_NULL, "Cannot add null item type"); + assertTrue(count > 0, "Count must be greater than zero"); + + uint8_t remaining = count; + + // Fill existing stacks of matching type first + for(uint32_t i = 0; i < inventory->inventorySize && remaining > 0; i++) { + itemstack_t *stack = &inventory->inventoryItems[i]; + if(stack->type != type) continue; + if(stack->count >= ITEM_STACK_COUNT_MAX) continue; + + uint8_t space = ITEM_STACK_COUNT_MAX - stack->count; + uint8_t toAdd = remaining < space ? remaining : space; + stack->count += toAdd; + remaining -= toAdd; + } + + // Fill empty slots with new stacks + for(uint32_t i = 0; i < inventory->inventorySize && remaining > 0; i++) { + itemstack_t *stack = &inventory->inventoryItems[i]; + if(stack->type != ITEM_TYPE_NULL) continue; + + uint8_t toAdd = remaining < ITEM_STACK_COUNT_MAX ? remaining : ITEM_STACK_COUNT_MAX; + stack->type = type; + stack->count = toAdd; + remaining -= toAdd; + } + + return count - remaining; +} + +uint8_t inventoryItemRemove( + inventory_t *inventory, + const itemtype_t type, + const uint8_t count +) { + assertNotNull(inventory, "Inventory must not be null"); + assertFalse(type == ITEM_TYPE_NULL, "Cannot remove null item type"); + assertTrue(count > 0, "Count must be greater than zero"); + + uint8_t remaining = count; + + for(uint32_t i = 0; i < inventory->inventorySize && remaining > 0; i++) { + itemstack_t *stack = &inventory->inventoryItems[i]; + if(stack->type != type) continue; + + uint8_t toRemove = remaining < stack->count ? remaining : stack->count; + stack->count -= toRemove; + remaining -= toRemove; + + if(stack->count == 0) stack->type = ITEM_TYPE_NULL; + } + + return count - remaining; +} + +uint8_t inventoryItemCount( + const inventory_t *inventory, + const itemtype_t type +) { + assertNotNull(inventory, "Inventory must not be null"); + + uint8_t total = 0; + for(uint32_t i = 0; i < inventory->inventorySize; i++) { + if(inventory->inventoryItems[i].type == type) { + total += inventory->inventoryItems[i].count; + } + } + return total; +} + +uint32_t inventoryStackCount(const inventory_t *inventory) { + assertNotNull(inventory, "Inventory must not be null"); + + uint32_t count = 0; + for(uint32_t i = 0; i < inventory->inventorySize; i++) { + if(inventory->inventoryItems[i].type != ITEM_TYPE_NULL) count++; + } + return count; +} + +size_t inventoryItemCountAll(const inventory_t *inventory) { + assertNotNull(inventory, "Inventory must not be null"); + + size_t total = 0; + for(uint32_t i = 0; i < inventory->inventorySize; i++) { + if(inventory->inventoryItems[i].type != ITEM_TYPE_NULL) { + total += inventory->inventoryItems[i].count; + } + } + return total; +} + +void inventoryItemSet( + inventory_t *inventory, + const itemtype_t type, + const uint8_t count +) { + assertNotNull(inventory, "Inventory must not be null"); + assertFalse(type == ITEM_TYPE_NULL, "Cannot set null item type"); + + // Find existing stack of this type + for(uint32_t i = 0; i < inventory->inventorySize; i++) { + if(inventory->inventoryItems[i].type != type) continue; + inventory->inventoryItems[i].count = count; + if(count == 0) inventory->inventoryItems[i].type = ITEM_TYPE_NULL; + return; + } + + // Find empty slot + for(uint32_t i = 0; i < inventory->inventorySize; i++) { + if(inventory->inventoryItems[i].type != ITEM_TYPE_NULL) continue; + inventory->inventoryItems[i].type = type; + inventory->inventoryItems[i].count = count; + return; + } + + assertUnreachable("Inventory is full, cannot set item"); +} + +void inventoryClear(inventory_t *inventory) { + assertNotNull(inventory, "Inventory must not be null"); + + for(uint32_t i = 0; i < inventory->inventorySize; i++) { + inventory->inventoryItems[i].type = ITEM_TYPE_NULL; + inventory->inventoryItems[i].count = 0; + } +} + +bool_t inventoryIsFull(const inventory_t *inventory) { + assertNotNull(inventory, "Inventory must not be null"); + + for(uint32_t i = 0; i < inventory->inventorySize; i++) { + itemstack_t *stack = &inventory->inventoryItems[i]; + if(stack->type == ITEM_TYPE_NULL) return false; + if(stack->count < ITEM_STACK_COUNT_MAX) return false; + } + return true; +} + +bool_t inventoryIsEmpty(const inventory_t *inventory) { + assertNotNull(inventory, "Inventory must not be null"); + + for(uint32_t i = 0; i < inventory->inventorySize; i++) { + if(inventory->inventoryItems[i].type != ITEM_TYPE_NULL) return false; + } + return true; +} + +bool_t inventoryItemHas( + const inventory_t *inventory, + const itemtype_t type, + const uint8_t count +) { + assertNotNull(inventory, "Inventory must not be null"); + return inventoryItemCount(inventory, type) >= count; +} + +bool_t inventoryItemCanAdd( + const inventory_t *inventory, + const itemtype_t type, + const uint8_t count +) { + assertNotNull(inventory, "Inventory must not be null"); + assertFalse(type == ITEM_TYPE_NULL, "Cannot check null item type"); + + uint32_t space = 0; + for(uint32_t i = 0; i < inventory->inventorySize; i++) { + itemstack_t *stack = &inventory->inventoryItems[i]; + if(stack->type == type) { + space += ITEM_STACK_COUNT_MAX - stack->count; + } else if(stack->type == ITEM_TYPE_NULL) { + space += ITEM_STACK_COUNT_MAX; + } + } + return space >= count; +} diff --git a/src/dusk/item/inventory.h b/src/dusk/item/inventory.h new file mode 100644 index 00000000..a79ee9bc --- /dev/null +++ b/src/dusk/item/inventory.h @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "itemstack.h" + +typedef struct { + itemstack_t *inventoryItems; + uint32_t inventorySize; +} inventory_t; + +/** + * Initializes an inventory with the given item stack array and size. + * + * @param inventory The inventory to initialize. + * @param inventoryItems The array of item stacks to use for the inventory. + * @param inventorySize The size of the inventory (number of item stacks). + */ +void inventoryInit( + inventory_t *inventory, + itemstack_t *inventoryItems, + const uint32_t inventorySize +); + +/** + * Adds items to the inventory, stacking them if possible. + * + * @param inventory The inventory to add items to. + * @param type The type of item to add. + * @param count The number of items to add. + * @return The number of items that were successfully added to the inventory. + */ +uint8_t inventoryItemAdd( + inventory_t *inventory, + const itemtype_t type, + const uint8_t count +); + +/** + * Removes items from the inventory, unstacking them if necessary. + * + * @param inventory The inventory to remove items from. + * @param type The type of item to remove. + * @param count The number of items to remove. + * @return The number of items that were successfully removed from the inventory. + */ +uint8_t inventoryItemRemove( + inventory_t *inventory, + const itemtype_t type, + const uint8_t count +); + +/** + * Get count of items of a type in the inventory. + * + * @param inventory The inventory to count items in. + * @param type The type of item to count. + * @return The number of items of the given type in the inventory. + */ +uint8_t inventoryItemCount( + const inventory_t *inventory, + const itemtype_t type +); + +/** + * Get count of STACKS in the inventory. + * + * @param inventory The inventory to count stacks in. + * @return The number of stacks in the inventory. + */ +uint32_t inventoryStackCount(const inventory_t *inventory); + +/** + * Get count of ITEMS in the inventory. + * + * @param inventory The inventory to count items in. + * @return The number of items in the inventory. + */ +size_t inventoryItemCountAll(const inventory_t *inventory); + +/** + * Sets the item stack at the given index in the inventory. + * + * @param inventory The inventory to set the item stack in. + * @param index The index of the item stack to set. + * @param type The type of item to set in the stack. + * @param count The number of items to set in the stack. + */ +void inventoryItemSet( + inventory_t *inventory, + const itemtype_t type, + const uint8_t count +); + +/** + * Clears all item stacks in the inventory. + * + * @param inventory The inventory to clear. + */ +void inventoryClear(inventory_t *inventory); + +/** + * Checks if the inventory is full. + * + * @param inventory The inventory to check. + * @return true if the inventory is full, false otherwise. + */ +bool_t inventoryIsFull(const inventory_t *inventory); + +/** + * Checks if the inventory is empty. + * + * @param inventory The inventory to check. + * @return true if the inventory is empty, false otherwise. + */ +bool_t inventoryIsEmpty(const inventory_t *inventory); + +/** + * Checks if the inventory has at least a certain number of items of a type. + * + * @param inventory The inventory to check. + * @param type The type of item to check for. + * @param count Count of item to check for. + * @return True if contained, false otherwise. + */ +bool_t inventoryItemHas( + const inventory_t *inventory, + const itemtype_t type, + const uint8_t count +); + +/** + * Checks if the inventory can add a certain number of items of a given type. + * + * @param inventory The inventory to check. + * @param type The type of item to check for. + * @param count Count of item to check for. + * @return True if can add, false otherwise. + */ +bool_t inventoryItemCanAdd( + const inventory_t *inventory, + const itemtype_t type, + const uint8_t count +); \ No newline at end of file diff --git a/src/dusk/item/itemstack.h b/src/dusk/item/itemstack.h new file mode 100644 index 00000000..e298b0bb --- /dev/null +++ b/src/dusk/item/itemstack.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "itemtype.h" + +#define ITEM_STACK_COUNT_MAX 99 + +typedef struct { + itemtype_t type; + uint8_t count; +} itemstack_t; \ No newline at end of file diff --git a/archive/rpg/item/inventory.h b/src/dusk/item/itemtype.h similarity index 51% rename from archive/rpg/item/inventory.h rename to src/dusk/item/itemtype.h index 8cdff101..580b3ecc 100644 --- a/archive/rpg/item/inventory.h +++ b/src/dusk/item/itemtype.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2025 Dominic Masters + * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT @@ -8,6 +8,10 @@ #pragma once #include "dusk.h" -typedef struct { - void *nothing; -} inventory_t; \ No newline at end of file +typedef enum { + ITEM_TYPE_NULL = 0, + + ITEM_TYPE_POTION, + + ITEM_TYPE_COUNT +} itemtype_t; \ No newline at end of file diff --git a/src/dusk/script/module/console/moduleconsole.h b/src/dusk/script/module/console/moduleconsole.h index 23941b7f..31a3555c 100644 --- a/src/dusk/script/module/console/moduleconsole.h +++ b/src/dusk/script/module/console/moduleconsole.h @@ -43,7 +43,7 @@ moduleBaseFunction(moduleConsoleGetVisible) { } moduleBaseFunction(moduleConsoleSetVisible) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; if(!jerry_value_is_boolean(args[0])) { return moduleBaseThrow("Console.visible: expected boolean"); } diff --git a/src/dusk/script/module/display/modulecolor.h b/src/dusk/script/module/display/modulecolor.h index 9cdefb77..d22a913d 100644 --- a/src/dusk/script/module/display/modulecolor.h +++ b/src/dusk/script/module/display/modulecolor.h @@ -42,7 +42,7 @@ moduleBaseFunction(moduleColorGetA) { } moduleBaseFunction(moduleColorSetR) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); color_t *c = moduleColorGet(callInfo); if(!c) return jerry_undefined(); c->r = (colorchannel8_t)jerry_value_as_number(args[0]); @@ -50,7 +50,7 @@ moduleBaseFunction(moduleColorSetR) { } moduleBaseFunction(moduleColorSetG) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); color_t *c = moduleColorGet(callInfo); if(!c) return jerry_undefined(); c->g = (colorchannel8_t)jerry_value_as_number(args[0]); @@ -58,7 +58,7 @@ moduleBaseFunction(moduleColorSetG) { } moduleBaseFunction(moduleColorSetB) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); color_t *c = moduleColorGet(callInfo); if(!c) return jerry_undefined(); c->b = (colorchannel8_t)jerry_value_as_number(args[0]); @@ -66,7 +66,7 @@ moduleBaseFunction(moduleColorSetB) { } moduleBaseFunction(moduleColorSetA) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); color_t *c = moduleColorGet(callInfo); if(!c) return jerry_undefined(); c->a = (colorchannel8_t)jerry_value_as_number(args[0]); diff --git a/src/dusk/script/module/display/modulespritebatch.h b/src/dusk/script/module/display/modulespritebatch.h new file mode 100644 index 00000000..25e0f557 --- /dev/null +++ b/src/dusk/script/module/display/modulespritebatch.h @@ -0,0 +1,169 @@ +/** + * 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 "script/module/display/modulecolor.h" +#include "script/module/math/modulevec2.h" +#include "script/module/math/modulevec3.h" +#include "display/spritebatch/spritebatch.h" + +static scriptproto_t MODULE_SPRITEBATCH_PROTO; + +moduleBaseFunction(moduleSpriteBatchGetSpriteCount) { + return jerry_number(SPRITEBATCH.spriteCount); +} + +moduleBaseFunction(moduleSpriteBatchPush) { +#if MESH_ENABLE_COLOR + if(argc < 9) return moduleBaseThrow("expected 9 arguments"); +#else + if(argc < 8) return moduleBaseThrow("expected 8 arguments"); +#endif + + moduleBaseRequireNumber(0); + moduleBaseRequireNumber(1); + moduleBaseRequireNumber(2); + moduleBaseRequireNumber(3); + +#if MESH_ENABLE_COLOR + if(!jerry_value_is_object(args[4])) { + return moduleBaseThrow("color must be a Color object"); + } + color_t *col = (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[4]); + if(!col) { + return moduleBaseThrow("color must be a Color object"); + } + moduleBaseRequireNumber(5); + moduleBaseRequireNumber(6); + moduleBaseRequireNumber(7); + moduleBaseRequireNumber(8); + spriteBatchPush( + (float_t)jerry_value_as_number(args[0]), + (float_t)jerry_value_as_number(args[1]), + (float_t)jerry_value_as_number(args[2]), + (float_t)jerry_value_as_number(args[3]), + *col, + (float_t)jerry_value_as_number(args[5]), + (float_t)jerry_value_as_number(args[6]), + (float_t)jerry_value_as_number(args[7]), + (float_t)jerry_value_as_number(args[8]) + ); +#else + moduleBaseRequireNumber(4); + moduleBaseRequireNumber(5); + moduleBaseRequireNumber(6); + moduleBaseRequireNumber(7); + spriteBatchPush( + (float_t)jerry_value_as_number(args[0]), + (float_t)jerry_value_as_number(args[1]), + (float_t)jerry_value_as_number(args[2]), + (float_t)jerry_value_as_number(args[3]), + (float_t)jerry_value_as_number(args[4]), + (float_t)jerry_value_as_number(args[5]), + (float_t)jerry_value_as_number(args[6]), + (float_t)jerry_value_as_number(args[7]) + ); +#endif + + return jerry_undefined(); +} + +moduleBaseFunction(moduleSpriteBatchPush3D) { +#if MESH_ENABLE_COLOR + if(argc < 5) return moduleBaseThrow("expected 5 arguments"); +#else + if(argc < 4) return moduleBaseThrow("expected 4 arguments"); +#endif + + if(!jerry_value_is_object(args[0])) { + return moduleBaseThrow("min must be a Vec3"); + } + float_t *min = moduleVec3From(args[0]); + if(!min) return moduleBaseThrow("min must be a Vec3"); + + if(!jerry_value_is_object(args[1])) { + return moduleBaseThrow("max must be a Vec3"); + } + float_t *max = moduleVec3From(args[1]); + if(!max) return moduleBaseThrow("max must be a Vec3"); + +#if MESH_ENABLE_COLOR + if(!jerry_value_is_object(args[2])) { + return moduleBaseThrow("color must be a Color object"); + } + color_t *col = (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[2]); + if(!col) { + return moduleBaseThrow("color must be a Color object"); + } + + if(!jerry_value_is_object(args[3])) { + return moduleBaseThrow("uvMin must be a Vec2"); + } + float_t *uvMin = moduleVec2From(args[3]); + if(!uvMin) return moduleBaseThrow("uvMin must be a Vec2"); + + if(!jerry_value_is_object(args[4])) { + return moduleBaseThrow("uvMax must be a Vec2"); + } + float_t *uvMax = moduleVec2From(args[4]); + if(!uvMax) return moduleBaseThrow("uvMax must be a Vec2"); + + spriteBatchPush3D(min, max, *col, uvMin, uvMax); +#else + if(!jerry_value_is_object(args[2])) { + return moduleBaseThrow("uvMin must be a Vec2"); + } + float_t *uvMin = moduleVec2From(args[2]); + if(!uvMin) return moduleBaseThrow("uvMin must be a Vec2"); + + if(!jerry_value_is_object(args[3])) { + return moduleBaseThrow("uvMax must be a Vec2"); + } + float_t *uvMax = moduleVec2From(args[3]); + if(!uvMax) return moduleBaseThrow("uvMax must be a Vec2"); + + spriteBatchPush3D(min, max, uvMin, uvMax); +#endif + + return jerry_undefined(); +} + +moduleBaseFunction(moduleSpriteBatchClear) { + spriteBatchClear(); + return jerry_undefined(); +} + +moduleBaseFunction(moduleSpriteBatchFlush) { + spriteBatchFlush(); + return jerry_undefined(); +} + +static void moduleSpriteBatch(void) { + scriptProtoInit( + &MODULE_SPRITEBATCH_PROTO, "SpriteBatch", sizeof(uint8_t), NULL + ); + + scriptProtoDefineStaticProp( + &MODULE_SPRITEBATCH_PROTO, "spriteCount", + moduleSpriteBatchGetSpriteCount, NULL + ); + + scriptProtoDefineStaticFunc( + &MODULE_SPRITEBATCH_PROTO, "push", moduleSpriteBatchPush + ); + scriptProtoDefineStaticFunc( + &MODULE_SPRITEBATCH_PROTO, "push3D", moduleSpriteBatchPush3D + ); + scriptProtoDefineStaticFunc( + &MODULE_SPRITEBATCH_PROTO, "clear", moduleSpriteBatchClear + ); + scriptProtoDefineStaticFunc( + &MODULE_SPRITEBATCH_PROTO, "flush", moduleSpriteBatchFlush + ); +} diff --git a/src/dusk/script/module/display/moduletext.h b/src/dusk/script/module/display/moduletext.h new file mode 100644 index 00000000..a1a4903e --- /dev/null +++ b/src/dusk/script/module/display/moduletext.h @@ -0,0 +1,73 @@ +/** + * 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 "script/module/display/modulecolor.h" +#include "display/text/text.h" + +static scriptproto_t MODULE_TEXT_PROTO; + +moduleBaseFunction(moduleTextDraw) { + if(argc < 3) return moduleBaseThrow("expected at least 3 arguments"); + moduleBaseRequireNumber(0); + moduleBaseRequireNumber(1); + if(!jerry_value_is_string(args[2])) return moduleBaseThrow("text must be a string"); + + float_t x = (float_t)jerry_value_as_number(args[0]); + float_t y = (float_t)jerry_value_as_number(args[1]); + + char_t text[1024]; + moduleBaseToString(args[2], text, sizeof(text)); + + color_t col = COLOR_WHITE; + if(argc >= 4 && jerry_value_is_object(args[3])) { + color_t *c = (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[3]); + if(c) col = *c; + } + + errorret_t err = textDraw( + x, y, text, col, &FONT_TILESET_DEFAULT, &FONT_TEXTURE_DEFAULT + ); + if(err.code != ERROR_OK) { + errorCatch(errorPrint(err)); + return moduleBaseThrow("Text draw failed"); + } + + return jerry_undefined(); +} + +moduleBaseFunction(moduleTextMeasure) { + if(argc < 1) return moduleBaseThrow("expected at least 1 argument"); + if(!jerry_value_is_string(args[0])) return moduleBaseThrow("text must be a string"); + + char_t text[1024]; + moduleBaseToString(args[0], text, sizeof(text)); + + int32_t w, h; + textMeasure(text, &FONT_TILESET_DEFAULT, &w, &h); + + jerry_value_t obj = jerry_object(); + jerry_value_t wKey = jerry_string_sz("width"); + jerry_value_t hKey = jerry_string_sz("height"); + jerry_value_t wVal = jerry_number(w); + jerry_value_t hVal = jerry_number(h); + jerry_object_set(obj, wKey, wVal); + jerry_object_set(obj, hKey, hVal); + jerry_value_free(wKey); + jerry_value_free(hKey); + jerry_value_free(wVal); + jerry_value_free(hVal); + return obj; +} + +static void moduleText(void) { + scriptProtoInit(&MODULE_TEXT_PROTO, "Text", sizeof(uint8_t), NULL); + scriptProtoDefineStaticFunc(&MODULE_TEXT_PROTO, "draw", moduleTextDraw); + scriptProtoDefineStaticFunc(&MODULE_TEXT_PROTO, "measure", moduleTextMeasure); +} diff --git a/src/dusk/script/module/entity/component/moduleentitycamera.h b/src/dusk/script/module/entity/component/moduleentitycamera.h index 12361743..87d54cc1 100644 --- a/src/dusk/script/module/entity/component/moduleentitycamera.h +++ b/src/dusk/script/module/entity/component/moduleentitycamera.h @@ -188,7 +188,7 @@ moduleBaseFunction(moduleEntityCameraSetProjectionType) { } moduleBaseFunction(moduleEntityCameraAdd) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); entityid_t id = (entityid_t)jerry_value_as_number(args[0]); componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_CAMERA); componenthandle_t h = { .eid = id, .cid = comp }; diff --git a/src/dusk/script/module/entity/component/moduleentitymaterial.h b/src/dusk/script/module/entity/component/moduleentitymaterial.h index bfd45fc9..da507559 100644 --- a/src/dusk/script/module/entity/component/moduleentitymaterial.h +++ b/src/dusk/script/module/entity/component/moduleentitymaterial.h @@ -28,7 +28,7 @@ static entitymaterial_t * moduleEntityMaterialGet( } moduleBaseFunction(moduleEntityMaterialSetColor) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; if(!jerry_value_is_object(args[0])) { return moduleBaseThrow("expected color object"); } @@ -68,7 +68,7 @@ moduleBaseFunction(moduleEntityMaterialSetColor) { } moduleBaseFunction(moduleEntityMaterialAdd) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); entityid_t id = (entityid_t)jerry_value_as_number(args[0]); componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MATERIAL); componenthandle_t h = { .eid = id, .cid = comp }; diff --git a/src/dusk/script/module/entity/component/moduleentitymesh.h b/src/dusk/script/module/entity/component/moduleentitymesh.h index e800b6bd..702cd77a 100644 --- a/src/dusk/script/module/entity/component/moduleentitymesh.h +++ b/src/dusk/script/module/entity/component/moduleentitymesh.h @@ -25,7 +25,7 @@ static entitymesh_t * moduleEntityMeshGet( } moduleBaseFunction(moduleEntityMeshAdd) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); entityid_t id = (entityid_t)jerry_value_as_number(args[0]); componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MESH); componenthandle_t h = { .eid = id, .cid = comp }; diff --git a/src/dusk/script/module/entity/component/moduleentityphysics.h b/src/dusk/script/module/entity/component/moduleentityphysics.h index dc1df368..614b99b0 100644 --- a/src/dusk/script/module/entity/component/moduleentityphysics.h +++ b/src/dusk/script/module/entity/component/moduleentityphysics.h @@ -32,7 +32,7 @@ moduleBaseFunction(moduleEntityPhysicsGetVelocity) { } moduleBaseFunction(moduleEntityPhysicsSetVelocity) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); if(!phys) return jerry_undefined(); vec3 v; @@ -56,7 +56,7 @@ moduleBaseFunction(moduleEntityPhysicsGetBodyType) { } moduleBaseFunction(moduleEntityPhysicsSetBodyType) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); if(!phys) return jerry_undefined(); phys->type = (physicsbodytype_t)(int32_t)jerry_value_as_number(args[0]); @@ -64,7 +64,7 @@ moduleBaseFunction(moduleEntityPhysicsSetBodyType) { } moduleBaseFunction(moduleEntityPhysicsApplyImpulse) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); if(!phys) return jerry_undefined(); if(phys->type == PHYSICS_BODY_STATIC) return jerry_undefined(); @@ -77,7 +77,7 @@ moduleBaseFunction(moduleEntityPhysicsApplyImpulse) { } moduleBaseFunction(moduleEntityPhysicsSetShapeCube) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); if(!phys) return jerry_undefined(); vec3 half; @@ -90,7 +90,7 @@ moduleBaseFunction(moduleEntityPhysicsSetShapeCube) { } moduleBaseFunction(moduleEntityPhysicsSetShapeSphere) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); if(!phys) return jerry_undefined(); phys->shape.type = PHYSICS_SHAPE_SPHERE; @@ -99,7 +99,7 @@ moduleBaseFunction(moduleEntityPhysicsSetShapeSphere) { } moduleBaseFunction(moduleEntityPhysicsSetShapeCapsule) { - moduleBaseRequireArgs(2); + if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");; moduleBaseRequireNumber(0); moduleBaseRequireNumber(1); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); if(!phys) return jerry_undefined(); @@ -111,7 +111,7 @@ moduleBaseFunction(moduleEntityPhysicsSetShapeCapsule) { } moduleBaseFunction(moduleEntityPhysicsSetShapePlane) { - moduleBaseRequireArgs(2); moduleBaseRequireNumber(1); + if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");; moduleBaseRequireNumber(1); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); if(!phys) return jerry_undefined(); vec3 normal; @@ -125,7 +125,7 @@ moduleBaseFunction(moduleEntityPhysicsSetShapePlane) { } moduleBaseFunction(moduleEntityPhysicsAdd) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); entityid_t id = (entityid_t)jerry_value_as_number(args[0]); componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_PHYSICS); componenthandle_t h = { .eid = id, .cid = comp }; diff --git a/src/dusk/script/module/entity/component/moduleentityposition.h b/src/dusk/script/module/entity/component/moduleentityposition.h index 693f7eb7..05063141 100644 --- a/src/dusk/script/module/entity/component/moduleentityposition.h +++ b/src/dusk/script/module/entity/component/moduleentityposition.h @@ -42,7 +42,7 @@ moduleBaseFunction(moduleEntityPositionGetPosition) { } moduleBaseFunction(moduleEntityPositionSetPosition) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; entityposition_t *pos = moduleEntityPositionGet(callInfo); if(!pos) return jerry_undefined(); vec3 v; @@ -63,7 +63,7 @@ moduleBaseFunction(moduleEntityPositionGetRotation) { } moduleBaseFunction(moduleEntityPositionSetRotation) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; entityposition_t *pos = moduleEntityPositionGet(callInfo); if(!pos) return jerry_undefined(); vec3 v; @@ -84,7 +84,7 @@ moduleBaseFunction(moduleEntityPositionGetScale) { } moduleBaseFunction(moduleEntityPositionSetScale) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; entityposition_t *pos = moduleEntityPositionGet(callInfo); if(!pos) return jerry_undefined(); vec3 v; @@ -97,7 +97,7 @@ moduleBaseFunction(moduleEntityPositionSetScale) { } moduleBaseFunction(moduleEntityPositionLookAt) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; entityposition_t *pos = moduleEntityPositionGet(callInfo); if(!pos) return jerry_undefined(); vec3 target; @@ -114,7 +114,7 @@ moduleBaseFunction(moduleEntityPositionLookAt) { } moduleBaseFunction(moduleEntityPositionAdd) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); entityid_t id = (entityid_t)jerry_value_as_number(args[0]); componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_POSITION); componenthandle_t h = { .eid = id, .cid = comp }; diff --git a/src/dusk/script/module/input/moduleinput.h b/src/dusk/script/module/input/moduleinput.h index 7923b8a2..107d5c0b 100644 --- a/src/dusk/script/module/input/moduleinput.h +++ b/src/dusk/script/module/input/moduleinput.h @@ -15,7 +15,7 @@ static scriptproto_t MODULE_INPUT_PROTO; // Static Methods moduleBaseFunction(moduleInputBind) { - moduleBaseRequireArgs(2); + if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");; moduleBaseRequireString(0); moduleBaseRequireNumber(1); @@ -40,7 +40,7 @@ moduleBaseFunction(moduleInputBind) { } moduleBaseFunction(moduleInputIsDown) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]); if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { return moduleBaseThrow("Input.isDown: invalid action"); @@ -49,7 +49,7 @@ moduleBaseFunction(moduleInputIsDown) { } moduleBaseFunction(moduleInputPressed) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]); if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { return moduleBaseThrow("Input.pressed: invalid action"); @@ -58,7 +58,7 @@ moduleBaseFunction(moduleInputPressed) { } moduleBaseFunction(moduleInputReleased) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]); if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { return moduleBaseThrow("Input.released: invalid action"); @@ -67,7 +67,7 @@ moduleBaseFunction(moduleInputReleased) { } moduleBaseFunction(moduleInputGetValue) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]); if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { return moduleBaseThrow("Input.getValue: invalid action"); @@ -76,7 +76,7 @@ moduleBaseFunction(moduleInputGetValue) { } moduleBaseFunction(moduleInputAxis) { - moduleBaseRequireArgs(2); + if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");; moduleBaseRequireNumber(0); moduleBaseRequireNumber(1); const inputaction_t neg = (inputaction_t)jerry_value_as_number(args[0]); const inputaction_t pos = (inputaction_t)jerry_value_as_number(args[1]); @@ -90,7 +90,7 @@ moduleBaseFunction(moduleInputAxis) { } moduleBaseFunction(moduleInputAxis2D) { - moduleBaseRequireArgs(4); + if(argc < 4) return moduleBaseThrow("Expected at least 4 arguments");; moduleBaseRequireNumber(0); moduleBaseRequireNumber(1); moduleBaseRequireNumber(2); moduleBaseRequireNumber(3); const inputaction_t negX = (inputaction_t)jerry_value_as_number(args[0]); diff --git a/src/dusk/script/module/math/modulemat4.h b/src/dusk/script/module/math/modulemat4.h index 94adabf1..49d81427 100644 --- a/src/dusk/script/module/math/modulemat4.h +++ b/src/dusk/script/module/math/modulemat4.h @@ -28,7 +28,7 @@ moduleBaseFunction(moduleMatConstructor) { } moduleBaseFunction(moduleMatMul) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t (*a)[4] = (float_t (*)[4])moduleMatGet(callInfo); if(!a) return moduleBaseThrow("Mat4.mul: invalid this"); float_t (*b)[4] = (float_t (*)[4])scriptProtoGetValue( @@ -63,7 +63,7 @@ moduleBaseFunction(moduleMatDeterminant) { } moduleBaseFunction(moduleMatMulVec3) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo); if(!m) return moduleBaseThrow("Mat4.mulVec3: invalid this"); vec3 vin; @@ -78,7 +78,7 @@ moduleBaseFunction(moduleMatMulVec3) { } moduleBaseFunction(moduleMatMulVec4) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo); if(!m) return moduleBaseThrow("Mat4.mulVec4: invalid this"); vec4 vin; @@ -91,7 +91,7 @@ moduleBaseFunction(moduleMatMulVec4) { } moduleBaseFunction(moduleMatTranslate) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo); if(!m) return moduleBaseThrow("Mat4.translate: invalid this"); vec3 tv; @@ -105,7 +105,7 @@ moduleBaseFunction(moduleMatTranslate) { } moduleBaseFunction(moduleMatScale) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo); if(!m) return moduleBaseThrow("Mat4.scale: invalid this"); vec3 sv; @@ -125,7 +125,7 @@ moduleBaseFunction(moduleMatStaticIdentity) { } moduleBaseFunction(moduleMatStaticPerspective) { - moduleBaseRequireArgs(4); + if(argc < 4) return moduleBaseThrow("Expected at least 4 arguments");; moduleBaseRequireNumber(0); moduleBaseRequireNumber(1); moduleBaseRequireNumber(2); moduleBaseRequireNumber(3); mat4 r; @@ -140,7 +140,7 @@ moduleBaseFunction(moduleMatStaticPerspective) { } moduleBaseFunction(moduleMatStaticLookAt) { - moduleBaseRequireArgs(3); + if(argc < 3) return moduleBaseThrow("Expected at least 3 arguments");; vec3 eye, center, up; if(!moduleVec3Check(args[0], eye)) { return moduleBaseThrow("Mat4.lookAt: eye must be a Vec3"); diff --git a/src/dusk/script/module/math/modulevec2.h b/src/dusk/script/module/math/modulevec2.h index cf52440c..13d2f4ec 100644 --- a/src/dusk/script/module/math/modulevec2.h +++ b/src/dusk/script/module/math/modulevec2.h @@ -55,7 +55,7 @@ moduleBaseFunction(moduleVec2SetY) { } moduleBaseFunction(moduleVec2Dot) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec2Get(callInfo); if(!a) return moduleBaseThrow("Vec2.dot: invalid this"); float_t *b = moduleVec2From(args[0]); @@ -92,7 +92,7 @@ moduleBaseFunction(moduleVec2Negate) { } moduleBaseFunction(moduleVec2Add) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec2Get(callInfo); if(!a) return moduleBaseThrow("Vec2.add: invalid this"); float_t *b = moduleVec2From(args[0]); @@ -103,7 +103,7 @@ moduleBaseFunction(moduleVec2Add) { } moduleBaseFunction(moduleVec2Sub) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec2Get(callInfo); if(!a) return moduleBaseThrow("Vec2.sub: invalid this"); float_t *b = moduleVec2From(args[0]); @@ -114,7 +114,7 @@ moduleBaseFunction(moduleVec2Sub) { } moduleBaseFunction(moduleVec2Scale) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); float_t *v = moduleVec2Get(callInfo); if(!v) return moduleBaseThrow("Vec2.scale: invalid this"); vec2 r; @@ -123,7 +123,7 @@ moduleBaseFunction(moduleVec2Scale) { } moduleBaseFunction(moduleVec2Lerp) { - moduleBaseRequireArgs(2); moduleBaseRequireNumber(1); + if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");; moduleBaseRequireNumber(1); float_t *a = moduleVec2Get(callInfo); if(!a) return moduleBaseThrow("Vec2.lerp: invalid this"); float_t *b = moduleVec2From(args[0]); @@ -134,7 +134,7 @@ moduleBaseFunction(moduleVec2Lerp) { } moduleBaseFunction(moduleVec2Distance) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec2Get(callInfo); if(!a) return moduleBaseThrow("Vec2.distance: invalid this"); float_t *b = moduleVec2From(args[0]); diff --git a/src/dusk/script/module/math/modulevec3.h b/src/dusk/script/module/math/modulevec3.h index 98d9a0dc..8bccc9f0 100644 --- a/src/dusk/script/module/math/modulevec3.h +++ b/src/dusk/script/module/math/modulevec3.h @@ -67,7 +67,7 @@ moduleBaseFunction(moduleVec3SetZ) { } moduleBaseFunction(moduleVec3Dot) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec3Get(callInfo); if(!a) return moduleBaseThrow("Vec3.dot: invalid this"); float_t *b = moduleVec3From(args[0]); @@ -76,7 +76,7 @@ moduleBaseFunction(moduleVec3Dot) { } moduleBaseFunction(moduleVec3Cross) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec3Get(callInfo); if(!a) return moduleBaseThrow("Vec3.cross: invalid this"); float_t *b = moduleVec3From(args[0]); @@ -115,7 +115,7 @@ moduleBaseFunction(moduleVec3Negate) { } moduleBaseFunction(moduleVec3Add) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec3Get(callInfo); if(!a) return moduleBaseThrow("Vec3.add: invalid this"); float_t *b = moduleVec3From(args[0]); @@ -126,7 +126,7 @@ moduleBaseFunction(moduleVec3Add) { } moduleBaseFunction(moduleVec3Sub) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec3Get(callInfo); if(!a) return moduleBaseThrow("Vec3.sub: invalid this"); float_t *b = moduleVec3From(args[0]); @@ -137,7 +137,7 @@ moduleBaseFunction(moduleVec3Sub) { } moduleBaseFunction(moduleVec3Scale) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); float_t *v = moduleVec3Get(callInfo); if(!v) return moduleBaseThrow("Vec3.scale: invalid this"); vec3 r; @@ -146,7 +146,7 @@ moduleBaseFunction(moduleVec3Scale) { } moduleBaseFunction(moduleVec3Lerp) { - moduleBaseRequireArgs(2); moduleBaseRequireNumber(1); + if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");; moduleBaseRequireNumber(1); float_t *a = moduleVec3Get(callInfo); if(!a) return moduleBaseThrow("Vec3.lerp: invalid this"); float_t *b = moduleVec3From(args[0]); @@ -157,7 +157,7 @@ moduleBaseFunction(moduleVec3Lerp) { } moduleBaseFunction(moduleVec3Distance) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec3Get(callInfo); if(!a) return moduleBaseThrow("Vec3.distance: invalid this"); float_t *b = moduleVec3From(args[0]); diff --git a/src/dusk/script/module/math/modulevec4.h b/src/dusk/script/module/math/modulevec4.h index 0e32046e..48ce003b 100644 --- a/src/dusk/script/module/math/modulevec4.h +++ b/src/dusk/script/module/math/modulevec4.h @@ -121,7 +121,7 @@ moduleBaseFunction(moduleVec4SetV1) { } moduleBaseFunction(moduleVec4Dot) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec4Get(callInfo); if(!a) return moduleBaseThrow("Vec4.dot: invalid this"); float_t *b = moduleVec4From(args[0]); @@ -158,7 +158,7 @@ moduleBaseFunction(moduleVec4Negate) { } moduleBaseFunction(moduleVec4Add) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec4Get(callInfo); if(!a) return moduleBaseThrow("Vec4.add: invalid this"); float_t *b = moduleVec4From(args[0]); @@ -169,7 +169,7 @@ moduleBaseFunction(moduleVec4Add) { } moduleBaseFunction(moduleVec4Sub) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; float_t *a = moduleVec4Get(callInfo); if(!a) return moduleBaseThrow("Vec4.sub: invalid this"); float_t *b = moduleVec4From(args[0]); @@ -180,7 +180,7 @@ moduleBaseFunction(moduleVec4Sub) { } moduleBaseFunction(moduleVec4Scale) { - moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireNumber(0); float_t *v = moduleVec4Get(callInfo); if(!v) return moduleBaseThrow("Vec4.scale: invalid this"); vec4 r; @@ -189,7 +189,7 @@ moduleBaseFunction(moduleVec4Scale) { } moduleBaseFunction(moduleVec4Lerp) { - moduleBaseRequireArgs(2); moduleBaseRequireNumber(1); + if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");; moduleBaseRequireNumber(1); float_t *a = moduleVec4Get(callInfo); if(!a) return moduleBaseThrow("Vec4.lerp: invalid this"); float_t *b = moduleVec4From(args[0]); diff --git a/src/dusk/script/module/module.h b/src/dusk/script/module/module.h index c5dc9984..ec6ab6f7 100644 --- a/src/dusk/script/module/module.h +++ b/src/dusk/script/module/module.h @@ -14,6 +14,8 @@ #include "script/module/time/moduletime.h" #include "script/module/display/modulecolor.h" #include "script/module/display/modulescreen.h" +#include "script/module/display/modulespritebatch.h" +#include "script/module/display/moduletext.h" #include "script/module/scene/modulescene.h" #include "script/module/console/moduleconsole.h" #include "script/module/engine/moduleengine.h" @@ -27,6 +29,8 @@ static void moduleRegister(void) { moduleTime(); moduleColor(); moduleScreen(); + moduleSpriteBatch(); + moduleText(); moduleScene(); moduleConsole(); moduleEngine(); diff --git a/src/dusk/script/module/modulebase.h b/src/dusk/script/module/modulebase.h index d6cf90cf..ddb26b59 100644 --- a/src/dusk/script/module/modulebase.h +++ b/src/dusk/script/module/modulebase.h @@ -213,14 +213,6 @@ static void moduleBaseCreateGlobalObject( jerry_value_free(global); } -/** - * Assert at least n arguments were passed; return type error if not. - */ -#define moduleBaseRequireArgs(n) \ - if((argc) < (n)) { \ - return moduleBaseThrow("Expected at least " #n " arguments"); \ - } - /** * Assert an argument is a number; return type error if not. */ diff --git a/src/dusk/script/module/scene/modulescene.h b/src/dusk/script/module/scene/modulescene.h index a97dffd3..29e68fa8 100644 --- a/src/dusk/script/module/scene/modulescene.h +++ b/src/dusk/script/module/scene/modulescene.h @@ -25,7 +25,7 @@ moduleBaseFunction(moduleSceneDefaultConstructor) { } moduleBaseFunction(moduleSceneSet) { - moduleBaseRequireArgs(1); + if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");; moduleBaseRequireString(0); char_t name[ASSET_FILE_PATH_MAX]; diff --git a/src/dusk/script/module/script/moduleinclude.h b/src/dusk/script/module/script/moduleinclude.h index 2ae49ce0..c7d927b8 100644 --- a/src/dusk/script/module/script/moduleinclude.h +++ b/src/dusk/script/module/script/moduleinclude.h @@ -7,6 +7,7 @@ #pragma once #include "script/module/modulebase.h" +#include "script/scriptmanager.h" moduleBaseFunction(moduleIncludeInclude) { if(argc < 1 || !jerry_value_is_string(args[0])) { @@ -25,32 +26,9 @@ moduleBaseFunction(moduleIncludeInclude) { return moduleBaseThrow("include: filename must end with .js"); } - char_t buffer[1024]; - stringCopy(buffer, filename, sizeof(buffer)); - - // Save and reset 'export' so the included module gets a clean undefined - // default. Saving lets nested includes each have their own export scope. - jerry_value_t global = jerry_current_realm(); - jerry_value_t moduleKey = jerry_string_sz("module"); - jerry_value_t prevModule = jerry_object_get(global, moduleKey); - jerry_value_t undef = jerry_undefined(); - jerry_object_set(global, moduleKey, undef); - jerry_value_free(undef); - - jerry_value_t result = 0; - errorret_t err = scriptManagerExecFile(buffer, &result); - if(result != 0) jerry_value_free(result); - - // Capture whatever the module assigned to 'module', then restore the - // caller's value so nested includes don't clobber each other. - jerry_value_t moduleVal = jerry_object_get(global, moduleKey); - jerry_object_set(global, moduleKey, prevModule); - jerry_value_free(prevModule); - jerry_value_free(moduleKey); - jerry_value_free(global); - + jerry_value_t moduleVal = 0; + errorret_t err = scriptInclude(filename, &moduleVal); if(err.code != ERROR_OK) { - jerry_value_free(moduleVal); errorCatch(errorPrint(err)); return moduleBaseThrow("Failed to include script file"); } diff --git a/src/dusk/script/scriptmanager.c b/src/dusk/script/scriptmanager.c index 657db8f5..9a9fed4a 100644 --- a/src/dusk/script/scriptmanager.c +++ b/src/dusk/script/scriptmanager.c @@ -77,6 +77,40 @@ errorret_t scriptManagerExecFile( return assetScriptLoad(fname, resultOut); } +errorret_t scriptInclude(const char_t *filename, jerry_value_t *out) { + assertNotNull(filename, "Filename cannot be NULL"); + + jerry_value_t global = jerry_current_realm(); + jerry_value_t moduleKey = jerry_string_sz("module"); + + jerry_value_t prevModule = jerry_object_get(global, moduleKey); + jerry_value_t undef = jerry_undefined(); + jerry_object_set(global, moduleKey, undef); + jerry_value_free(undef); + + jerry_value_t execResult = 0; + errorret_t err = scriptManagerExecFile(filename, &execResult); + if(execResult != 0) jerry_value_free(execResult); + + jerry_value_t moduleVal = jerry_object_get(global, moduleKey); + jerry_object_set(global, moduleKey, prevModule); + jerry_value_free(prevModule); + jerry_value_free(moduleKey); + jerry_value_free(global); + + if(err.code != ERROR_OK) { + jerry_value_free(moduleVal); + return err; + } + + if(out != NULL) { + *out = moduleVal; + } else { + jerry_value_free(moduleVal); + } + errorOk(); +} + errorret_t scriptManagerDispose(void) { jerry_cleanup(); errorOk(); diff --git a/src/dusk/script/scriptmanager.h b/src/dusk/script/scriptmanager.h index 70a4861d..32507e50 100644 --- a/src/dusk/script/scriptmanager.h +++ b/src/dusk/script/scriptmanager.h @@ -56,6 +56,18 @@ errorret_t scriptManagerExecFile( jerry_value_t *result ); +/** + * Execute a JS file using the module-export pattern and return the exported + * value. The script is expected to assign its public API to the global + * `module` variable. The caller owns the returned jerry_value_t and must + * call jerry_value_free() on it when done. + * + * @param filename Path to the .js asset file. + * @param out Receives the value assigned to `module` by the script. + * @return The error return value. + */ +errorret_t scriptInclude(const char_t *filename, jerry_value_t *out); + /** * Dispose of the script manager. * diff --git a/src/dusk/ui/CMakeLists.txt b/src/dusk/ui/CMakeLists.txt index 48e4dbab..071692b8 100644 --- a/src/dusk/ui/CMakeLists.txt +++ b/src/dusk/ui/CMakeLists.txt @@ -8,4 +8,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ui.c uifps.c + uielement.c ) \ No newline at end of file diff --git a/src/dusk/ui/ui.c b/src/dusk/ui/ui.c index 47c83d00..a81777d9 100644 --- a/src/dusk/ui/ui.c +++ b/src/dusk/ui/ui.c @@ -10,13 +10,20 @@ #include "assert/assert.h" #include "display/spritebatch/spritebatch.h" #include "display/screen/screen.h" -#include "ui/uifps.h" -#include "console/console.h" +#include "ui/uielement.h" +#include "log/log.h" ui_t UI; errorret_t uiInit(void) { memoryZero(&UI, sizeof(ui_t)); + + uielement_t *element = &UI_ELEMENTS[0]; + while(element->type != UI_ELEMENT_TYPE_NULL) { + errorChain(uiElementInit(element)); + element++; + } + errorOk(); } @@ -24,10 +31,16 @@ void uiUpdate(void) { } errorret_t uiRender(void) { - errorChain(uiFPSDraw()); - errorChain(spriteBatchFlush()); - errorChain(consoleDraw()); - errorChain(spriteBatchFlush()); + const uielement_t *element = &UI_ELEMENTS[0]; + while(element->type != UI_ELEMENT_TYPE_NULL) { + errorChain(uiElementDraw(element)); + + if(SPRITEBATCH.spriteCount > 0) { + logDebug("Finished UI element but unflushed sprites remain.\n"); + } + + element++; + } errorOk(); } diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c new file mode 100644 index 00000000..33cba7f9 --- /dev/null +++ b/src/dusk/ui/uielement.c @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uielement.h" +#include "assert/assert.h" +#include "script/scriptmanager.h" +#include "console/console.h" +#include "ui/uifps.h" + +uielement_t UI_ELEMENTS[] = { + { .type = UI_ELEMENT_TYPE_NATIVE, .native = { .draw = consoleDraw } }, + { .type = UI_ELEMENT_TYPE_NATIVE, .native = { .draw = uiFPSDraw } }, + { .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } }, + + { .type = UI_ELEMENT_TYPE_NULL }, +}; + +errorret_t uiElementInit(uielement_t *element) { + assertNotNull(element, "element must not be NULL"); + + if(element->type == UI_ELEMENT_TYPE_SCRIPT) { + errorChain(scriptInclude(element->script.script, &element->script.module)); + } + + errorOk(); +} + +errorret_t uiElementDraw(const uielement_t *element) { + switch(element->type) { + case UI_ELEMENT_TYPE_NATIVE: + errorChain(element->native.draw()); + break; + + case UI_ELEMENT_TYPE_SCRIPT: { + jerry_value_t renderKey = jerry_string_sz("render"); + jerry_value_t renderFn = jerry_object_get(element->script.module, renderKey); + jerry_value_free(renderKey); + + if(!jerry_value_is_function(renderFn)) { + jerry_value_free(renderFn); + errorThrow("UI script module has no render function"); + } + + jerry_value_t ret = jerry_call(renderFn, element->script.module, NULL, 0); + jerry_value_free(renderFn); + + if(jerry_value_is_exception(ret)) { + jerry_value_t errVal = jerry_exception_value(ret, 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(ret); + errorThrow("UI script render error: %s", buf); + } + + jerry_value_free(ret); + break; + } + + default: + assertUnreachable("Invalid UI element type"); + } + + errorOk(); +} \ No newline at end of file diff --git a/src/dusk/ui/uielement.h b/src/dusk/ui/uielement.h new file mode 100644 index 00000000..c6ee657b --- /dev/null +++ b/src/dusk/ui/uielement.h @@ -0,0 +1,46 @@ +/** + * 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 enum { + UI_ELEMENT_TYPE_NULL, + UI_ELEMENT_TYPE_NATIVE, + UI_ELEMENT_TYPE_SCRIPT, + UI_ELEMENT_TYPE_COUNT +} uielementtype_t; + +typedef struct { + uielementtype_t type; + + union { + struct { + errorret_t (*draw)(); + } native; + + struct { + const char_t *script; + jerry_value_t module; + } script; + }; +} uielement_t; + +extern uielement_t UI_ELEMENTS[]; + +/** + * Initializes a UI element. + */ +errorret_t uiElementInit(uielement_t *element); + +/** + * Draws a UI element. + * + * @param element The element to render. + * @return Any error that occurs. + */ +errorret_t uiElementDraw(const uielement_t *element); \ No newline at end of file diff --git a/src/dusk/ui/uifps.c b/src/dusk/ui/uifps.c index 6a9a908d..afb50edc 100644 --- a/src/dusk/ui/uifps.c +++ b/src/dusk/ui/uifps.c @@ -53,6 +53,6 @@ errorret_t uiFPSDraw() { fpsText, textColor, &FONT_TILESET_DEFAULT, &FONT_TEXTURE_DEFAULT )); - - errorOk(); + + return spriteBatchFlush(); } \ No newline at end of file