From 1172ad50b49c6fe22d2a09235f20c4dcab27bffd Mon Sep 17 00:00:00 2001 From: Ratstail91 Date: Thu, 15 Jun 2023 03:10:30 +1000 Subject: [PATCH] Implemented node child sorting --- Airport.vcxproj | 1 + assets/scripts/empty.toy | 4 + assets/scripts/gameplay/drone.toy | 5 +- assets/scripts/gameplay/lejana.toy | 5 +- assets/scripts/gameplay/scene.toy | 36 +++++---- assets/scripts/gameplay/text.toy | 2 + assets/scripts/gameplay/tilemap.toy | 19 ++++- assets/scripts/sorting_test.toy | 34 +++++++++ box/box_node.c | 111 +++++++++++++++++++++++++++- box/box_node.h | 12 ++- box/lib_node.c | 37 ++++++++++ 11 files changed, 235 insertions(+), 31 deletions(-) create mode 100644 assets/scripts/sorting_test.toy diff --git a/Airport.vcxproj b/Airport.vcxproj index 14a001b..2f4d4aa 100644 --- a/Airport.vcxproj +++ b/Airport.vcxproj @@ -35,6 +35,7 @@ + 17.0 diff --git a/assets/scripts/empty.toy b/assets/scripts/empty.toy index 79bacfa..cd8b2b4 100644 --- a/assets/scripts/empty.toy +++ b/assets/scripts/empty.toy @@ -1 +1,5 @@ //this file is a polyfill TODO: fix this + +fn getRealPos(node: opaque) { + return [0, 0]; +} \ No newline at end of file diff --git a/assets/scripts/gameplay/drone.toy b/assets/scripts/gameplay/drone.toy index 31bebb6..785ce7d 100644 --- a/assets/scripts/gameplay/drone.toy +++ b/assets/scripts/gameplay/drone.toy @@ -120,8 +120,9 @@ fn onFree(node: opaque) { node.freeNodeTexture(); } -fn customOnDraw(node: opaque, parentX: int, parentY: int) { - node.drawNode(realX + parentX - SPRITE_WIDTH / 4, realY + parentY - SPRITE_HEIGHT / 2, SPRITE_WIDTH, SPRITE_HEIGHT); +fn onDraw(node: opaque) { + var camera = parent.callNodeFn("getCameraPos"); + node.drawNode(realX + camera[0] - SPRITE_WIDTH / 4, realY + camera[1] - SPRITE_HEIGHT / 2, SPRITE_WIDTH, SPRITE_HEIGHT); } diff --git a/assets/scripts/gameplay/lejana.toy b/assets/scripts/gameplay/lejana.toy index c4b73d5..510b9e3 100644 --- a/assets/scripts/gameplay/lejana.toy +++ b/assets/scripts/gameplay/lejana.toy @@ -197,8 +197,9 @@ fn onFree(node: opaque) { node.freeNodeTexture(); } -fn customOnDraw(node: opaque, parentX: int, parentY: int) { - node.drawNode(realX + parentX - SPRITE_WIDTH / 4, realY + parentY - SPRITE_HEIGHT / 2, SPRITE_WIDTH, SPRITE_HEIGHT); +fn onDraw(node: opaque) { + var camera = parent.callNodeFn("getCameraPos"); + node.drawNode(realX + camera[0] - SPRITE_WIDTH / 4, realY + camera[1] - SPRITE_HEIGHT / 2, SPRITE_WIDTH, SPRITE_HEIGHT); } diff --git a/assets/scripts/gameplay/scene.toy b/assets/scripts/gameplay/scene.toy index 51c848f..58c3a3e 100644 --- a/assets/scripts/gameplay/scene.toy +++ b/assets/scripts/gameplay/scene.toy @@ -9,6 +9,9 @@ var TILE_HEIGHT: int const = 32; var MAP_WIDTH: int const = 16; var MAP_HEIGHT: int const = 16; +var CAMERA_X: int const = 0; +var CAMERA_Y: int const = 32; + var tilemap: opaque = null; var player: opaque = null; @@ -16,8 +19,6 @@ var stepCounter: opaque = null; var enemies: [opaque] = []; -var entities: [opaque] = null; //full list of entities - var collisionMap: [bool] = null; //cache this, since it won't change during a level var rng: opaque = null; @@ -25,9 +26,12 @@ var rng: opaque = null; //lifecycle functions fn onLoad(node: opaque) { - tilemap = node.loadChild("scripts:/gameplay/tilemap.toy"); + var empty = node.loadChild("scripts:/empty.toy"); + + tilemap = empty.loadChild("scripts:/gameplay/tilemap.toy"); + stepCounter = empty.loadChild("scripts:/gameplay/step-counter.toy"); + player = node.loadChild("scripts:/gameplay/lejana.toy"); - stepCounter = node.loadChild("scripts:/gameplay/step-counter.toy"); } fn onInit(node: opaque) { @@ -39,17 +43,12 @@ fn onFree(node: opaque) { rng.freeRandomGenerator(); } -fn onStep(node: opaque) { - entities = entities.sort(depthComparator); -} +//fn onStep(node: opaque) { +// // +//} fn onDraw(node: opaque) { - //call each child's custom draw fn (with parent shifting) - tilemap.callNodeFn("customOnDraw", 0, 32); - for (var i = 0; i < entities.length(); i++) { - entities[i].callNodeFn("customOnDraw", 0, 32); - } - + node.sortChildrenNode(depthComparator); stepCounter.callNodeFn("customOnDraw", 0, 0, TILE_WIDTH * MAP_WIDTH, 32); } @@ -64,8 +63,7 @@ fn loadChild(parent: opaque, fname: string) { fn generateLevel(node: opaque, seed: int, width: int, height: int) { //reset the array enemies = []; - entities = [player]; //assume the player exists already - + if (rng != null) rng.freeRandomGenerator(); rng = createRandomGenerator(seed); @@ -83,7 +81,6 @@ fn generateLevel(node: opaque, seed: int, width: int, height: int) { d.initNode(); enemies.push(d); - entities.push(d); var x = 0; var y = 0; @@ -134,8 +131,15 @@ fn depthComparator(lhs: opaque, rhs: opaque) { //for sorting by depth var rhsPos = rhs.callNodeFn("getRealPos"); if (lhsPos[1] == rhsPos[1]) { //BUGFIX: prevent z-fighting + if (lhsPos[0] == rhsPos[0]) { + return true; + } return lhsPos[0] < rhsPos[0]; } return lhsPos[1] < rhsPos[1]; +} + +fn getCameraPos(node: opaque) { + return [CAMERA_X, CAMERA_Y]; } \ No newline at end of file diff --git a/assets/scripts/gameplay/text.toy b/assets/scripts/gameplay/text.toy index 6b5c647..a03cd04 100644 --- a/assets/scripts/gameplay/text.toy +++ b/assets/scripts/gameplay/text.toy @@ -1,5 +1,7 @@ import node; +//this is a child of the step counter, which simply renders text + fn customOnDraw(node: opaque, parentX: int, parentY: int) { node.drawNode(parentX, parentY); } diff --git a/assets/scripts/gameplay/tilemap.toy b/assets/scripts/gameplay/tilemap.toy index 314fb09..6acbd7d 100644 --- a/assets/scripts/gameplay/tilemap.toy +++ b/assets/scripts/gameplay/tilemap.toy @@ -41,7 +41,10 @@ var tileset: [string : [int]] = [ ]; -//debug vars +var parent: opaque = null; + + +//debug vars - what are these for? var camX = 0; var camY = 0; var camW = 1080; @@ -53,10 +56,20 @@ fn onLoad(node: opaque) { node.loadNodeTexture("sprites:/tileset.png"); } -fn customOnDraw(node: opaque, parentX: int, parentY: int) { +fn onInit(node: opaque) { + //find root + parent = node; + while (parent.getParentNode() != null) { + parent = parent.getParentNode(); + } +} + +fn onDraw(node: opaque) { if (tilemap == null) { return; } + + var camera = parent.callNodeFn("getCameraPos"); //calc the region to render var lowerX: int = round((camX - camW/2.0) / TILE_WIDTH); @@ -77,7 +90,7 @@ fn customOnDraw(node: opaque, parentX: int, parentY: int) { for (var i = lowerX; i < upperX; i++) { node.setNodeRect(tilemap[j * mapWidth * 2 + i * 2] * 16, tilemap[j * mapWidth * 2 + i * 2 + 1] * 16, 16, 16); - node.drawNode(i * TILE_WIDTH + parentX, j * TILE_HEIGHT + parentY, TILE_WIDTH, TILE_HEIGHT); + node.drawNode(i * TILE_WIDTH + camera[0], j * TILE_HEIGHT + camera[1], TILE_WIDTH, TILE_HEIGHT); } } } diff --git a/assets/scripts/sorting_test.toy b/assets/scripts/sorting_test.toy new file mode 100644 index 0000000..b4bbdfd --- /dev/null +++ b/assets/scripts/sorting_test.toy @@ -0,0 +1,34 @@ + +import node; + +//generate a number of child nodes +fn onInit(node: opaque) { + node.loadChild("scripts:/empty.toy", 3); + node.loadChild("scripts:/empty.toy", 2); + node.loadChild("scripts:/empty.toy", 1); + node.loadChild("scripts:/empty.toy", 4); + node.loadChild("scripts:/empty.toy", 5); + + node.freeChildNode(3); +} + +fn onStep(node) { + node.sortChildrenNode(lessThan); +} + +fn lessThan(lhs, rhs) { + var a = lhs.callNodeFn("getValue"); + var b = rhs.callNodeFn("getValue"); + + return a < b; +} + +//utils - polyfills +fn loadChild(parent: opaque, fname: string, value) { + var child: opaque = loadNode(fname); + + child.callNodeFn("setValue", value); + + parent.pushNode(child); + return child; +} \ No newline at end of file diff --git a/box/box_node.c b/box/box_node.c index eff529b..2c82842 100644 --- a/box/box_node.c +++ b/box/box_node.c @@ -111,6 +111,115 @@ void Box_freeChildNode(Box_Node* node, int index) { node->children[index] = NULL; } +static void swapUtil(Box_Node** lhs, Box_Node** rhs) { + Box_Node* tmp = *lhs; + *lhs = *rhs; + *rhs = tmp; +} + +//copied from lib_standard.c +static void recursiveLiteralQuicksortUtil(Toy_Interpreter* interpreter, Box_Node** ptr, int count, Toy_Literal fnCompare) { + //base case + if (count <= 1) { + return; + } + + int runner = 0; + + //iterate through the array + for (int checker = 0; checker < count - 1; checker++) { + //if node is null, it is always sorted to the end + if (ptr[checker] == NULL) { + swapUtil(&ptr[checker], &ptr[checker + 1]); + runner++; + continue; + } + + if (ptr[checker + 1] == NULL) { + runner++; + continue; + } + + Toy_LiteralArray arguments; + Toy_LiteralArray returns; + + Toy_initLiteralArray(&arguments); + Toy_initLiteralArray(&returns); + + Toy_pushLiteralArray(&arguments, TOY_TO_OPAQUE_LITERAL(ptr[checker], OPAQUE_TAG_NODE)); + Toy_pushLiteralArray(&arguments, TOY_TO_OPAQUE_LITERAL(ptr[checker + 1], OPAQUE_TAG_NODE)); + + Toy_callLiteralFn(interpreter, fnCompare, &arguments, &returns); + + Toy_Literal lessThan = Toy_popLiteralArray(&returns); + + Toy_freeLiteralArray(&arguments); + Toy_freeLiteralArray(&returns); + + if (TOY_IS_TRUTHY(lessThan)) { + swapUtil(&ptr[runner++], &ptr[checker]); + } + + Toy_freeLiteral(lessThan); + } + + //"shift everything up" so the pivot is in the middle + swapUtil(&ptr[runner], &ptr[count - 1]); + + //recurse on each end + if (runner > 0) { + recursiveLiteralQuicksortUtil(interpreter, &ptr[0], runner, fnCompare); + } + + if (runner < count) { + recursiveLiteralQuicksortUtil(interpreter, &ptr[runner + 1], count - runner - 1, fnCompare); + } +} + +BOX_API void Box_sortChildrenNode(Box_Node* node, Toy_Interpreter* interpreter, Toy_Literal fnCompare) { + //check that this node's children aren't already sorted + bool sorted = true; + for (int checker = 0; checker < node->count - 1 && sorted; checker++) { + //NULL (tombstone) is always considered unsorted + if (node->children[checker] == NULL || node->children[checker + 1] == NULL) { + sorted = false; + break; + } + + Toy_LiteralArray arguments; + Toy_LiteralArray returns; + + Toy_initLiteralArray(&arguments); + Toy_initLiteralArray(&returns); + + Toy_pushLiteralArray(&arguments, TOY_TO_OPAQUE_LITERAL(node->children[checker], OPAQUE_TAG_NODE)); + Toy_pushLiteralArray(&arguments, TOY_TO_OPAQUE_LITERAL(node->children[checker + 1], OPAQUE_TAG_NODE)); + + Toy_callLiteralFn(interpreter, fnCompare, &arguments, &returns); + + Toy_Literal lessThan = Toy_popLiteralArray(&returns); + + Toy_freeLiteralArray(&arguments); + Toy_freeLiteralArray(&returns); + + if (!TOY_IS_TRUTHY(lessThan)) { + sorted = false; + } + + Toy_freeLiteral(lessThan); + } + + //sort the children + if (!sorted) { + recursiveLiteralQuicksortUtil(interpreter, node->children, node->count, fnCompare); + } + + //re-count the newly-sorted children + for (int i = node->count - 1; node->children[i] == NULL; i--) { + node->count--; + } +} + Toy_Literal Box_callNodeLiteral(Box_Node* node, Toy_Interpreter* interpreter, Toy_Literal key, Toy_LiteralArray* args) { Toy_Literal ret = TOY_TO_NULL_LITERAL; @@ -287,6 +396,6 @@ void Box_setTextNode(Box_Node* node, TTF_Font* font, const char* text, SDL_Color void Box_drawNode(Box_Node* node, SDL_Rect dest) { if (!node->texture) return; SDL_Rect src = node->rect; - src.x += src.w * node->currentFrame; //TODO: improve this + src.x += src.w * node->currentFrame; SDL_RenderCopy(engine.renderer, node->texture, &src, &dest); } diff --git a/box/box_node.h b/box/box_node.h index 53e0df5..b223e75 100644 --- a/box/box_node.h +++ b/box/box_node.h @@ -9,13 +9,9 @@ //forward declare typedef struct Box_private_node Box_Node; -// typedef void (*Box_NodeCallback)(void*); //the node object, which forms a tree typedef struct Box_private_node { - //function for releasing memory NOTE: removed, because it's not needed with only 1 node type - I've left them commented out because I might need them soon - // Box_NodeCallback freeMemory; - //toy functions, stored in a dict for flexibility Toy_LiteralDictionary* functions; @@ -36,10 +32,10 @@ typedef struct Box_private_node { //rendering-specific features SDL_Texture* texture; - SDL_Rect rect; - int frames; + SDL_Rect rect; //rendered rect + int frames; //horizontal-strip based animations int currentFrame; -} Box_Node; //TODO: rename this? +} Box_Node; BOX_API void Box_initNode(Box_Node* node, Toy_Interpreter* interpreter, const unsigned char* tb, size_t size); //run bytecode, then grab all top-level function literals BOX_API void Box_pushNode(Box_Node* node, Box_Node* child); //push to the array (prune tombstones when expanding/copying) @@ -48,6 +44,8 @@ BOX_API void Box_freeNode(Box_Node* node); //free this node and all children BOX_API Box_Node* Box_getChildNode(Box_Node* node, int index); BOX_API void Box_freeChildNode(Box_Node* node, int index); +BOX_API void Box_sortChildrenNode(Box_Node* node, Toy_Interpreter* interpreter, Toy_Literal fnCompare); + BOX_API Toy_Literal Box_callNodeLiteral(Box_Node* node, Toy_Interpreter* interpreter, Toy_Literal key, Toy_LiteralArray* args); BOX_API Toy_Literal Box_callNode(Box_Node* node, Toy_Interpreter* interpreter, const char* fnName, Toy_LiteralArray* args); //call "fnName" on this node, and only this node, if it exists diff --git a/box/lib_node.c b/box/lib_node.c index 86af3f3..7e4cd5e 100644 --- a/box/lib_node.c +++ b/box/lib_node.c @@ -257,6 +257,42 @@ static int nativeFreeChildNode(Toy_Interpreter* interpreter, Toy_LiteralArray* a return 0; } +static int nativeSortChildrenNode(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) { + if (arguments->count != 2) { + interpreter->errorOutput("Incorrect number of arguments passed to sortChildrenNode\n"); + return -1; + } + + Toy_Literal fnLiteral = Toy_popLiteralArray(arguments); + Toy_Literal nodeLiteral = Toy_popLiteralArray(arguments); + + Toy_Literal nodeLiteralIdn = nodeLiteral; //annoying + if (TOY_IS_IDENTIFIER(nodeLiteral) && Toy_parseIdentifierToValue(interpreter, &nodeLiteral)) { + Toy_freeLiteral(nodeLiteralIdn); + } + + Toy_Literal fnLiteralIdn = fnLiteral; //annoying + if (TOY_IS_IDENTIFIER(fnLiteral) && Toy_parseIdentifierToValue(interpreter, &fnLiteral)) { + Toy_freeLiteral(fnLiteralIdn); + } + + //check argument types + if (!TOY_IS_OPAQUE(nodeLiteral) || !TOY_IS_FUNCTION(fnLiteral)) { + interpreter->errorOutput("Incorrect argument type passed to sortChildrenNode\n"); + Toy_freeLiteral(nodeLiteral); + return -1; + } + + Box_Node* node = TOY_AS_OPAQUE(nodeLiteral); + + Box_sortChildrenNode(node, interpreter, fnLiteral); + + //cleanup + Toy_freeLiteral(nodeLiteral); + Toy_freeLiteral(fnLiteral); + return 0; +} + static int nativeGetParentNode(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) { //checks if (arguments->count != 1) { @@ -1099,6 +1135,7 @@ int Box_hookNode(Toy_Interpreter* interpreter, Toy_Literal identifier, Toy_Liter {"pushNode", nativePushNode}, {"getChildNode", nativeGetChildNode}, {"freeChildNode", nativeFreeChildNode}, + {"sortChildrenNode", nativeSortChildrenNode}, {"getParentNode", nativeGetParentNode}, {"getChildNodeCount", nativeGetChildNodeCount}, {"loadNodeTexture", nativeLoadNodeTexture},