diff --git a/src/AssetManager.c b/src/AssetManager.c index 5841c67..dba1e1e 100644 --- a/src/AssetManager.c +++ b/src/AssetManager.c @@ -18,10 +18,525 @@ #include "Dengine/AssetManager.h" #include "Dengine/Debug.h" +#include "Dengine/MemoryArena.h" #include "Dengine/OpenGL.h" #include "Dengine/Platform.h" -INTERNAL AtlasSubTexture *getAtlasSubTexture(TexAtlas *atlas, char *key) +/* + ********************************* + * XML Operations + ********************************* + */ +enum XmlTokenType +{ + xmltokentype_unknown, + xmltokentype_openArrow, + xmltokentype_closeArrow, + xmltokentype_name, + xmltokentype_value, + xmltokentype_equals, + xmltokentype_quotes, + xmltokentype_backslash, + xmltokentype_count, +}; + +typedef struct XmlToken +{ + // TODO(doyle): Dynamic size string in tokens maybe. + enum XmlTokenType type; + char string[128]; + i32 len; +} XmlToken; + +INTERNAL XmlToken *tokeniseXmlBuffer(MemoryArena *arena, char *buffer, + i32 bufferSize, int *numTokens) +{ + XmlToken *xmlTokens = PLATFORM_MEM_ALLOC(arena, 8192, XmlToken); + i32 tokenIndex = 0; + for (i32 i = 0; i < bufferSize; i++) + { + char c = (CAST(char *) buffer)[i]; + switch (c) + { + case '<': + case '>': + case '=': + case '/': + { + + enum XmlTokenType type = xmltokentype_unknown; + if (c == '<') + { + type = xmltokentype_openArrow; + } + else if (c == '>') + { + type = xmltokentype_closeArrow; + } + else if (c == '=') + { + type = xmltokentype_equals; + } + else + { + type = xmltokentype_backslash; + } + + xmlTokens[tokenIndex].type = type; + xmlTokens[tokenIndex].len = 1; + tokenIndex++; + break; + } + + case '"': + { + xmlTokens[tokenIndex].type = xmltokentype_value; + for (i32 j = i + 1; j < bufferSize; j++) + { + char c = (CAST(char *) buffer)[j]; + + if (c == '"') + { + break; + } + else + { + xmlTokens[tokenIndex].string[xmlTokens[tokenIndex].len++] = + c; +#ifdef DENGINE_DEBUG + ASSERT(xmlTokens[tokenIndex].len < + ARRAY_COUNT(xmlTokens[tokenIndex].string)); +#endif + } + } + + // NOTE(doyle): +1 to skip the closing quotes + i += (xmlTokens[tokenIndex].len + 1); + tokenIndex++; + break; + } + + default: + { + if ((c >= 'a' && c <= 'z') || c >= 'A' && c <= 'Z') + { + xmlTokens[tokenIndex].type = xmltokentype_name; + for (i32 j = i; j < bufferSize; j++) + { + char c = (CAST(char *) buffer)[j]; + + if (c == ' ' || c == '=' || c == '>' || c == '<' || + c == '\\') + { + break; + } + else + { + xmlTokens[tokenIndex] + .string[xmlTokens[tokenIndex].len++] = c; +#ifdef DENGINE_DEBUG + ASSERT(xmlTokens[tokenIndex].len < + ARRAY_COUNT(xmlTokens[tokenIndex].string)); +#endif + } + } + i += xmlTokens[tokenIndex].len; + tokenIndex++; + } + break; + } + } + } + + // TODO(doyle): Dynamic token allocation + *numTokens = 8192; + return xmlTokens; +} + +INTERNAL XmlNode *buildXmlTree(MemoryArena *arena, XmlToken *xmlTokens, + i32 numTokens) +{ + XmlNode *root = PLATFORM_MEM_ALLOC(arena, 1, XmlNode); + if (!root) return NULL; + + XmlNode *node = root; + node->parent = node; + + // NOTE(doyle): Used for when closing a node with many children. We + // automatically assign the next child after each child close within + // a group. Hence on the last child, we open another node but the next + // token indicates the group is closing- we need to set the last child's + // next reference to NULL + XmlNode *prevNode = NULL; + for (i32 i = 0; i < numTokens; i++) + { + XmlToken *token = &xmlTokens[i]; + + switch (token->type) + { + + case xmltokentype_openArrow: + { + /* Open arrows indicate closing parent node or new node name */ + XmlToken *nextToken = &xmlTokens[++i]; + if (nextToken->type == xmltokentype_backslash) + { + nextToken = &xmlTokens[++i]; + if (common_strcmp(nextToken->string, node->parent->name) == 0) + { + node->parent->isClosed = TRUE; + + if (prevNode) + { + prevNode->next = NULL; + } + + XmlNode *parent = node->parent; + PLATFORM_MEM_FREE(arena, node, sizeof(XmlNode)); + node = node->parent; + } + else + { +#ifdef DENGINE_DEBUG + DEBUG_LOG( + "Closing xml node name does not match parent name"); +#endif + } + } + else if (nextToken->type == xmltokentype_name) + { + node->name = nextToken->string; + } + else + { +#ifdef DENGINE_DEBUG + DEBUG_LOG("Unexpected token type after open arrow"); +#endif + } + + token = nextToken; + break; + } + + case xmltokentype_name: + { + // TODO(doyle): Store latest attribute pointer so we aren't always + // chasing the linked list each iteration. Do the same for children + // node + + /* Xml Attributes are a linked list, get first free entry */ + XmlAttribute *attribute = &node->attribute; + if (attribute->init) + { + while (attribute->next) + attribute = attribute->next; + + attribute->next = PLATFORM_MEM_ALLOC(arena, 1, XmlAttribute); + attribute = attribute->next; + } + + /* Just plain text is a node attribute name */ + attribute->name = token->string; + + /* Followed by the value */ + token = &xmlTokens[++i]; + attribute->value = token->string; + attribute->init = TRUE; + break; + } + + case xmltokentype_closeArrow: + { + XmlToken prevToken = xmlTokens[i - 1]; + prevNode = node; + + /* Closed node means we can return to parent */ + if (prevToken.type == xmltokentype_backslash) + { + node->isClosed = TRUE; + node = node->parent; + } + + if (!node->isClosed) + { + /* Unclosed node means next fields will be children of node */ + + /* If the first child is free allocate, otherwise we have to + * iterate through the child's next node(s) */ + if (!node->child) + { + // TODO(doyle): Mem alloc error checking + node->child = PLATFORM_MEM_ALLOC(arena, 1, XmlNode); + node->child->parent = node; + node = node->child; + } + else + { + XmlNode *nodeToCheck = node->child; + while (nodeToCheck->next) + nodeToCheck = nodeToCheck->next; + + nodeToCheck->next = PLATFORM_MEM_ALLOC(arena, 1, XmlNode); + nodeToCheck->next->parent = node; + node = nodeToCheck->next; + } + } + break; + } + + default: + { + break; + } + + } + } + + return root; +} + +INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena *arena, + XmlNode *root) +{ + XmlNode *node = root; + while (node) + { + if (common_strcmp(node->name, "TextureAtlas") == 0) + { + XmlNode *atlasXmlNode = node; + TexAtlas *atlasEntry = NULL; + if (common_strcmp(node->attribute.name, "imagePath") == 0) + { + char *imageName = atlasXmlNode->attribute.value; + atlasEntry = asset_makeTexAtlas(assetManager, arena, imageName); + + char *dataDir = "data/textures/WorldTraveller/"; + char imagePath[512] = {0}; + common_strncat(imagePath, dataDir, common_strlen(dataDir)); + common_strncat(imagePath, imageName, common_strlen(imageName)); + + asset_loadTextureImage(assetManager, arena, imagePath, + imageName); + + atlasEntry->key = PLATFORM_MEM_ALLOC( + arena, common_strlen(imageName) + 1, char); + common_strncpy(atlasEntry->key, imageName, + common_strlen(imageName)); + + atlasEntry->tex = asset_getTex(assetManager, imageName); + + XmlNode *atlasChildNode = atlasXmlNode->child; + while (atlasChildNode) + { + if (common_strcmp(atlasChildNode->name, "SubTexture") == 0) + { + XmlAttribute *subTextureAttrib = + &atlasChildNode->attribute; + + AtlasSubTexture newSubTexEntry = {0}; + while (subTextureAttrib) + { + + // TODO(doyle): Work around for now in xml reading, + // reading the last node closing node not being + // merged to the parent + if (!subTextureAttrib->name) continue; + + if (common_strcmp(subTextureAttrib->name, "name") == + 0) + { + char *value = subTextureAttrib->value; + newSubTexEntry.key = value; + } + else if (common_strcmp(subTextureAttrib->name, + "x") == 0) + { + char *value = subTextureAttrib->value; + i32 valueLen = common_strlen(value); + i32 intValue = common_atoi(value, valueLen); + + newSubTexEntry.rect.pos.x = CAST(f32) intValue; + } + else if (common_strcmp(subTextureAttrib->name, + "y") == 0) + { + char *value = subTextureAttrib->value; + i32 valueLen = common_strlen(value); + + i32 intValue = common_atoi(value, valueLen); + newSubTexEntry.rect.pos.y = CAST(f32) intValue; + } + else if (common_strcmp(subTextureAttrib->name, + "width") == 0) + { + char *value = subTextureAttrib->value; + i32 valueLen = common_strlen(value); + i32 intValue = common_atoi(value, valueLen); + + newSubTexEntry.rect.size.w = CAST(f32) intValue; + } + else if (common_strcmp(subTextureAttrib->name, + "height") == 0) + { + char *value = subTextureAttrib->value; + i32 valueLen = common_strlen(value); + i32 intValue = common_atoi(value, valueLen); + + newSubTexEntry.rect.size.h = CAST(f32) intValue; + } + else + { +#ifdef DENGINE_DEBUG + DEBUG_LOG( + "Unsupported xml attribute in SubTexture"); +#endif + } + subTextureAttrib = subTextureAttrib->next; + } + + // TODO(doyle): XML specifies 0,0 top left, we + // prefer 0,0 bottom right, so offset by size since 0,0 + // is top left and size creates a bounding box below it + newSubTexEntry.rect.pos.y = + 1024 - newSubTexEntry.rect.pos.y; + newSubTexEntry.rect.pos.y -= newSubTexEntry.rect.size.h; + +#ifdef DENGINE_DEBUG + ASSERT(newSubTexEntry.key) +#endif + + u32 subTexHashIndex = common_murmurHash2( + newSubTexEntry.key, + common_strlen(newSubTexEntry.key), 0xDEADBEEF); + subTexHashIndex = + subTexHashIndex % ARRAY_COUNT(atlasEntry->subTex); + + // NOTE(doyle): Hash collision + AtlasSubTexture *subTexEntry = + &atlasEntry->subTex[subTexHashIndex]; + if (subTexEntry->key) + { +#ifdef DENGINE_DEBUG + + // NOTE(doyle): Two textures have the same access + // name + ASSERT(common_strcmp(subTexEntry->key, + newSubTexEntry.key) != 0); +#endif + while (subTexEntry->next) + subTexEntry = subTexEntry->next; + + subTexEntry->next = + PLATFORM_MEM_ALLOC(arena, 1, AtlasSubTexture); + subTexEntry = subTexEntry->next; + } + + *subTexEntry = newSubTexEntry; + i32 keyLen = common_strlen(newSubTexEntry.key); + + subTexEntry->key = + PLATFORM_MEM_ALLOC(arena, keyLen + 1, char); + common_strncpy(subTexEntry->key, newSubTexEntry.key, + keyLen); + } + else + { +#ifdef DENGINE_DEBUG + DEBUG_LOG("Unsupported xml node name not parsed"); +#endif + } + + atlasChildNode = atlasChildNode->next; + } + } + else + { +#ifdef DENGINE_DEBUG + DEBUG_LOG("Unsupported xml node"); +#endif + } + } + else + { +#ifdef DENGINE_DEBUG + DEBUG_LOG("Unsupported xml node name not parsed"); +#endif + } + node = node->next; + } +} + +INTERNAL void recursiveFreeXmlTree(MemoryArena *arena, XmlNode *node) +{ + if (!node) + { + return; + } else + { + // NOTE(doyle): First attribute is statically allocated, only if there's + // more attributes do we dynamically allocate + XmlAttribute *attrib = node->attribute.next; + + while (attrib) + { + XmlAttribute *next = attrib->next; + + attrib->name = NULL; + attrib->value = NULL; + PLATFORM_MEM_FREE(arena, attrib, sizeof(XmlAttribute)); + attrib = next; + } + + recursiveFreeXmlTree(arena, node->child); + recursiveFreeXmlTree(arena, node->next); + + node->name = NULL; + node->isClosed = FALSE; + PLATFORM_MEM_FREE(arena, node, sizeof(XmlNode)); + } +} + +INTERNAL void freeXmlData(MemoryArena *arena, XmlToken *tokens, i32 numTokens, + XmlNode *tree) +{ + if (tree) recursiveFreeXmlTree(arena, tree); + if (tokens) PLATFORM_MEM_FREE(arena, tokens, numTokens * sizeof(XmlToken)); +} + +i32 asset_loadXmlFile(AssetManager *assetManager, MemoryArena *arena, + PlatformFileRead *fileRead) +{ + i32 result = 0; + /* Tokenise buffer */ + i32 numTokens = 0; + XmlToken *xmlTokens = + tokeniseXmlBuffer(arena, fileRead->buffer, fileRead->size, &numTokens); + + /* Build XML tree from tokens */ + XmlNode *xmlTree = buildXmlTree(arena, xmlTokens, numTokens); + + if (xmlTree) + { + /* Parse XML tree to game structures */ + parseXmlTreeToGame(assetManager, arena, xmlTree); + } + else + { + result = -1; + } + + /* Free data */ + freeXmlData(arena, xmlTokens, numTokens, xmlTree); + + return result; +} + +/* + ********************************* + * Texture Operations + ********************************* + */ +INTERNAL AtlasSubTexture *getAtlasSubTex(TexAtlas *atlas, char *key) { u32 hashIndex = common_getHashIndex(key, ARRAY_COUNT(atlas->subTex)); AtlasSubTexture *result = &atlas->subTex[hashIndex]; @@ -34,8 +549,8 @@ INTERNAL AtlasSubTexture *getAtlasSubTexture(TexAtlas *atlas, char *key) return result; } -INTERNAL AtlasSubTexture *makeAtlasSubTexture(TexAtlas *atlas, - MemoryArena *arena, char *key) +INTERNAL AtlasSubTexture *getFreeAtlasSubTexSlot(TexAtlas *atlas, + MemoryArena *arena, char *key) { u32 hashIndex = common_getHashIndex(key, ARRAY_COUNT(atlas->subTex)); AtlasSubTexture *result = &atlas->subTex[hashIndex]; @@ -59,8 +574,8 @@ INTERNAL AtlasSubTexture *makeAtlasSubTexture(TexAtlas *atlas, return result; } -INTERNAL Animation *getFreeAnimSlot(Animation *table, u32 tableSize, - MemoryArena *arena, char *key) +INTERNAL Animation *getFreeAnimationSlot(Animation *table, u32 tableSize, + MemoryArena *arena, char *key) { u32 hashIndex = common_getHashIndex(key, tableSize); Animation *result = &table[hashIndex]; @@ -84,9 +599,9 @@ INTERNAL Animation *getFreeAnimSlot(Animation *table, u32 tableSize, return result; } -Rect asset_getAtlasSubTexRect(TexAtlas *atlas, char *key) +Rect asset_getSubTexRect(TexAtlas *atlas, char *key) { - AtlasSubTexture *subTex = getAtlasSubTexture(atlas, key); + AtlasSubTexture *subTex = getAtlasSubTex(atlas, key); Rect result = subTex->rect; return result; } @@ -527,7 +1042,7 @@ const i32 asset_loadTTFont(AssetManager *assetManager, MemoryArena *arena, char charTmp[2] = {0}; charTmp[0] = charToEncode; AtlasSubTexture *subTex = - makeAtlasSubTexture(fontAtlas, arena, charTmp); + getFreeAtlasSubTexSlot(fontAtlas, arena, charTmp); subTex->key = PLATFORM_MEM_ALLOC(arena, 1, char); subTex->key[0] = charToEncode; @@ -615,7 +1130,7 @@ void asset_addAnimation(AssetManager *assetManager, MemoryArena *arena, char *animName, TexAtlas *atlas, char **subTextureNames, i32 numSubTextures, f32 frameDuration) { - Animation *anim = getFreeAnimSlot( + Animation *anim = getFreeAnimationSlot( assetManager->anims, ARRAY_COUNT(assetManager->anims), arena, animName); anim->atlas = atlas; @@ -629,7 +1144,7 @@ void asset_addAnimation(AssetManager *assetManager, MemoryArena *arena, anim->frameList = PLATFORM_MEM_ALLOC(arena, numSubTextures, char*); for (i32 i = 0; i < numSubTextures; i++) { - AtlasSubTexture *subTex = getAtlasSubTexture(atlas, subTextureNames[i]); + AtlasSubTexture *subTex = getAtlasSubTex(atlas, subTextureNames[i]); anim->frameList[i] = subTex->key; } @@ -656,3 +1171,29 @@ v2 asset_stringDimInPixels(const Font *const font, const char *const string) return stringDim; } + +void asset_unitTest(MemoryArena *arena) +{ + PlatformFileRead xmlFileRead = {0}; + i32 result = platform_readFileToBuffer( + arena, "data/textures/WorldTraveller/ClaudeSprite.xml", &xmlFileRead); + if (result) + { + DEBUG_LOG( + "unitTest() error: Could not load XML file for memory free test"); + } + else + { + /* Tokenise buffer */ + i32 memBefore = arena->bytesAllocated; + i32 numTokens = 0; + XmlToken *xmlTokens = tokeniseXmlBuffer(arena, xmlFileRead.buffer, + xmlFileRead.size, &numTokens); + /* Build XML tree from tokens */ + XmlNode *xmlTree = buildXmlTree(arena, xmlTokens, numTokens); + freeXmlData(arena, xmlTokens, numTokens, xmlTree); + i32 memAfter = arena->bytesAllocated; + + ASSERT(memBefore == memAfter); + } +} diff --git a/src/Debug.c b/src/Debug.c index 8d4d9d2..4e363a5 100644 --- a/src/Debug.c +++ b/src/Debug.c @@ -103,6 +103,36 @@ void debug_init(MemoryArena *arena, v2 windowSize, Font font) GLOBAL_debug.initialConsoleP = V2(consoleXPos, consoleYPos); } +void debug_recursivePrintXmlTree(XmlNode *root, i32 levelsDeep) +{ + if (!root) + { + return; + } + else + { + for (i32 i = 0; i < levelsDeep; i++) + { + printf("-"); + } + + printf("%s ", root->name); + + XmlAttribute *attribute = &root->attribute; + printf("| %s = %s", attribute->name, attribute->value); + + while (attribute->next) + { + attribute = attribute->next; + printf("| %s = %04s", attribute->name, attribute->value); + } + printf("\n"); + + debug_recursivePrintXmlTree(root->child, levelsDeep+1); + debug_recursivePrintXmlTree(root->next, levelsDeep); + } +} + void debug_callCountIncrement(i32 id) { ASSERT(id < debugcallcount_num); diff --git a/src/Entity.c b/src/Entity.c index 148dbf0..06972a4 100644 --- a/src/Entity.c +++ b/src/Entity.c @@ -56,7 +56,7 @@ void entity_updateAnim(Entity *entity, f32 dt) case entitytype_npc: char *frameName = anim->frameList[currEntityAnim->currFrame]; Rect texRect = - asset_getAtlasSubTexRect(anim->atlas, frameName); + asset_getSubTexRect(anim->atlas, frameName); entity->renderSize = texRect.size; default: break; diff --git a/src/Renderer.c b/src/Renderer.c index f6fc9b2..640efa4 100644 --- a/src/Renderer.c +++ b/src/Renderer.c @@ -211,7 +211,7 @@ void renderer_string(Renderer *const renderer, MemoryArena *arena, Rect camera, /* Get texture out */ Rect charTexRect = - asset_getAtlasSubTexRect(font->atlas, &CAST(char)codepoint); + asset_getSubTexRect(font->atlas, &CAST(char)codepoint); v4 deprecatedTexRect = {0}; deprecatedTexRect.vec2[0] = charTexRect.pos; @@ -251,7 +251,7 @@ void renderer_entity(Renderer *renderer, Rect camera, Entity *entity, EntityAnim *entityAnim = &entity->animList[entity->currAnimId]; Animation *anim = entityAnim->anim; char *frameName = anim->frameList[entityAnim->currFrame]; - Rect animRect = asset_getAtlasSubTexRect(anim->atlas, frameName); + Rect animRect = asset_getSubTexRect(anim->atlas, frameName); // TODO(doyle): Switch to rect v4 animTexRect = {0}; diff --git a/src/WorldTraveller.c b/src/WorldTraveller.c index 87fef8d..1317013 100644 --- a/src/WorldTraveller.c +++ b/src/WorldTraveller.c @@ -111,541 +111,6 @@ INTERNAL void rendererInit(GameState *state, v2 windowSize) #endif } -typedef struct XmlAttribute -{ - b32 init; - char *name; - char *value; - - struct XmlAttribute *next; -} XmlAttribute; - -typedef struct XmlNode -{ - char *name; - XmlAttribute attribute; - - // NOTE(doyle): Track if node has more children - b32 isClosed; - - // NOTE(doyle): Direct child/parent - struct XmlNode *parent; - struct XmlNode *child; - - // NOTE(doyle): Else all other nodes - struct XmlNode *next; -} XmlNode; - -enum XmlTokenType -{ - xmltokentype_unknown, - xmltokentype_openArrow, - xmltokentype_closeArrow, - xmltokentype_name, - xmltokentype_value, - xmltokentype_equals, - xmltokentype_quotes, - xmltokentype_backslash, - xmltokentype_count, -}; - -typedef struct XmlToken -{ - // TODO(doyle): Dynamic size string in tokens maybe. - enum XmlTokenType type; - char string[128]; - i32 len; -} XmlToken; - -#define DEBUG_RECURSIVE_PRINT_XML_TREE(sig) debug_recursivePrintXmlTree(sig, 1) -INTERNAL void debug_recursivePrintXmlTree(XmlNode *root, i32 levelsDeep) -{ - if (!root) - { - return; - } - else - { - for (i32 i = 0; i < levelsDeep; i++) - { - printf("-"); - } - - printf("%s ", root->name); - - XmlAttribute *attribute = &root->attribute; - printf("| %s = %s", attribute->name, attribute->value); - - while (attribute->next) - { - attribute = attribute->next; - printf("| %s = %04s", attribute->name, attribute->value); - } - printf("\n"); - - debug_recursivePrintXmlTree(root->child, levelsDeep+1); - debug_recursivePrintXmlTree(root->next, levelsDeep); - } -} - -INTERNAL XmlToken *tokeniseXmlBuffer(MemoryArena *arena, char *buffer, - i32 bufferSize, int *numTokens) -{ - /* - ********************** - * Tokenise XML Buffer - ********************** - */ - XmlToken *xmlTokens = PLATFORM_MEM_ALLOC(arena, 8192, XmlToken); - i32 tokenIndex = 0; - for (i32 i = 0; i < bufferSize; i++) - { - char c = (CAST(char *) buffer)[i]; - switch (c) - { - case '<': - case '>': - case '=': - case '/': - { - - enum XmlTokenType type = xmltokentype_unknown; - if (c == '<') - { - type = xmltokentype_openArrow; - } - else if (c == '>') - { - type = xmltokentype_closeArrow; - } - else if (c == '=') - { - type = xmltokentype_equals; - } - else - { - type = xmltokentype_backslash; - } - - xmlTokens[tokenIndex].type = type; - xmlTokens[tokenIndex].len = 1; - tokenIndex++; - break; - } - - case '"': - { - xmlTokens[tokenIndex].type = xmltokentype_value; - for (i32 j = i + 1; j < bufferSize; j++) - { - char c = (CAST(char *) buffer)[j]; - - if (c == '"') - { - break; - } - else - { - xmlTokens[tokenIndex].string[xmlTokens[tokenIndex].len++] = - c; -#ifdef DENGINE_DEBUG - ASSERT(xmlTokens[tokenIndex].len < - ARRAY_COUNT(xmlTokens[tokenIndex].string)); -#endif - } - } - - // NOTE(doyle): +1 to skip the closing quotes - i += (xmlTokens[tokenIndex].len + 1); - tokenIndex++; - break; - } - - default: - { - if ((c >= 'a' && c <= 'z') || c >= 'A' && c <= 'Z') - { - xmlTokens[tokenIndex].type = xmltokentype_name; - for (i32 j = i; j < bufferSize; j++) - { - char c = (CAST(char *) buffer)[j]; - - if (c == ' ' || c == '=' || c == '>' || c == '<' || - c == '\\') - { - break; - } - else - { - xmlTokens[tokenIndex] - .string[xmlTokens[tokenIndex].len++] = c; -#ifdef DENGINE_DEBUG - ASSERT(xmlTokens[tokenIndex].len < - ARRAY_COUNT(xmlTokens[tokenIndex].string)); -#endif - } - } - i += xmlTokens[tokenIndex].len; - tokenIndex++; - } - break; - } - } - } - - // TODO(doyle): Dynamic token allocation - *numTokens = 8192; - return xmlTokens; -} - -INTERNAL XmlNode *buildXmlTree(MemoryArena *arena, XmlToken *xmlTokens, - i32 numTokens) -{ - XmlNode *root = PLATFORM_MEM_ALLOC(arena, 1, XmlNode); - XmlNode *node = root; - node->parent = node; - - // NOTE(doyle): Used for when closing a node with many children. We - // automatically assign the next child after each child close within - // a group. Hence on the last child, we open another node but the next - // token indicates the group is closing- we need to set the last child's - // next reference to NULL - XmlNode *prevNode = NULL; - for (i32 i = 0; i < numTokens; i++) - { - XmlToken *token = &xmlTokens[i]; - - switch (token->type) - { - - case xmltokentype_openArrow: - { - /* Open arrows indicate closing parent node or new node name */ - XmlToken *nextToken = &xmlTokens[++i]; - if (nextToken->type == xmltokentype_backslash) - { - nextToken = &xmlTokens[++i]; - if (common_strcmp(nextToken->string, node->parent->name) == 0) - { - node->parent->isClosed = TRUE; - - if (prevNode) - { - prevNode->next = NULL; - } - - XmlNode *parent = node->parent; - PLATFORM_MEM_FREE(arena, node, sizeof(XmlNode)); - - node = node->parent; - } - else - { -#ifdef DENGINE_DEBUG - DEBUG_LOG( - "Closing xml node name does not match parent name"); -#endif - } - } - else if (nextToken->type == xmltokentype_name) - { - node->name = nextToken->string; - } - else - { -#ifdef DENGINE_DEBUG - DEBUG_LOG("Unexpected token type after open arrow"); -#endif - } - - token = nextToken; - break; - } - - case xmltokentype_name: - { - // TODO(doyle): Store latest attribute pointer so we aren't always - // chasing the linked list each iteration. Do the same for children - // node - - /* Xml Attributes are a linked list, get first free entry */ - XmlAttribute *attribute = &node->attribute; - if (attribute->init) - { - while (attribute->next) - attribute = attribute->next; - - attribute->next = PLATFORM_MEM_ALLOC(arena, 1, XmlAttribute); - attribute = attribute->next; - } - - /* Just plain text is a node attribute name */ - attribute->name = token->string; - - /* Followed by the value */ - token = &xmlTokens[++i]; - attribute->value = token->string; - attribute->init = TRUE; - break; - } - - case xmltokentype_closeArrow: - { - XmlToken prevToken = xmlTokens[i - 1]; - prevNode = node; - - /* Closed node means we can return to parent */ - if (prevToken.type == xmltokentype_backslash) - { - node->isClosed = TRUE; - node = node->parent; - } - - if (!node->isClosed) - { - /* Unclosed node means next fields will be children of node */ - - /* If the first child is free allocate, otherwise we have to - * iterate through the child's next node(s) */ - if (!node->child) - { - node->child = PLATFORM_MEM_ALLOC(arena, 1, XmlNode); - node->child->parent = node; - node = node->child; - } - else - { - XmlNode *nodeToCheck = node->child; - while (nodeToCheck->next) - nodeToCheck = nodeToCheck->next; - - nodeToCheck->next = PLATFORM_MEM_ALLOC(arena, 1, XmlNode); - nodeToCheck->next->parent = node; - node = nodeToCheck->next; - } - } - break; - } - - default: - { - break; - } - - } - } - - return root; -} - -INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena *arena, - XmlNode *root) -{ - XmlNode *node = root; - while (node) - { - if (common_strcmp(node->name, "TextureAtlas") == 0) - { - XmlNode *atlasXmlNode = node; - TexAtlas *atlasEntry = NULL; - if (common_strcmp(node->attribute.name, "imagePath") == 0) - { - char *imageName = atlasXmlNode->attribute.value; - atlasEntry = asset_makeTexAtlas(assetManager, arena, imageName); - - char *dataDir = "data/textures/WorldTraveller/"; - char imagePath[512] = {0}; - common_strncat(imagePath, dataDir, common_strlen(dataDir)); - common_strncat(imagePath, imageName, common_strlen(imageName)); - - asset_loadTextureImage(assetManager, arena, imagePath, - imageName); - - atlasEntry->key = PLATFORM_MEM_ALLOC( - arena, common_strlen(imageName) + 1, char); - common_strncpy(atlasEntry->key, imageName, - common_strlen(imageName)); - - atlasEntry->tex = asset_getTex(assetManager, imageName); - - XmlNode *atlasChildNode = atlasXmlNode->child; - while (atlasChildNode) - { - if (common_strcmp(atlasChildNode->name, "SubTexture") == 0) - { - XmlAttribute *subTextureAttrib = - &atlasChildNode->attribute; - - AtlasSubTexture newSubTexEntry = {0}; - while (subTextureAttrib) - { - - // TODO(doyle): Work around for now in xml reading, - // reading the last node closing node not being - // merged to the parent - if (!subTextureAttrib->name) continue; - - if (common_strcmp(subTextureAttrib->name, "name") == - 0) - { - char *value = subTextureAttrib->value; - newSubTexEntry.key = value; - } - else if (common_strcmp(subTextureAttrib->name, - "x") == 0) - { - char *value = subTextureAttrib->value; - i32 valueLen = common_strlen(value); - i32 intValue = common_atoi(value, valueLen); - - newSubTexEntry.rect.pos.x = CAST(f32) intValue; - } - else if (common_strcmp(subTextureAttrib->name, - "y") == 0) - { - char *value = subTextureAttrib->value; - i32 valueLen = common_strlen(value); - - i32 intValue = common_atoi(value, valueLen); - newSubTexEntry.rect.pos.y = CAST(f32) intValue; - } - else if (common_strcmp(subTextureAttrib->name, - "width") == 0) - { - char *value = subTextureAttrib->value; - i32 valueLen = common_strlen(value); - i32 intValue = common_atoi(value, valueLen); - - newSubTexEntry.rect.size.w = CAST(f32) intValue; - } - else if (common_strcmp(subTextureAttrib->name, - "height") == 0) - { - char *value = subTextureAttrib->value; - i32 valueLen = common_strlen(value); - i32 intValue = common_atoi(value, valueLen); - - newSubTexEntry.rect.size.h = CAST(f32) intValue; - } - else - { -#ifdef DENGINE_DEBUG - DEBUG_LOG( - "Unsupported xml attribute in SubTexture"); -#endif - } - subTextureAttrib = subTextureAttrib->next; - } - - // TODO(doyle): XML specifies 0,0 top left, we - // prefer 0,0 bottom right, so offset by size since 0,0 - // is top left and size creates a bounding box below it - newSubTexEntry.rect.pos.y = - 1024 - newSubTexEntry.rect.pos.y; - newSubTexEntry.rect.pos.y -= newSubTexEntry.rect.size.h; - -#ifdef DENGINE_DEBUG - ASSERT(newSubTexEntry.key) -#endif - - u32 subTexHashIndex = common_murmurHash2( - newSubTexEntry.key, - common_strlen(newSubTexEntry.key), 0xDEADBEEF); - subTexHashIndex = - subTexHashIndex % ARRAY_COUNT(atlasEntry->subTex); - - // NOTE(doyle): Hash collision - AtlasSubTexture *subTexEntry = - &atlasEntry->subTex[subTexHashIndex]; - if (subTexEntry->key) - { -#ifdef DENGINE_DEBUG - - // NOTE(doyle): Two textures have the same access - // name - ASSERT(common_strcmp(subTexEntry->key, - newSubTexEntry.key) != 0); -#endif - while (subTexEntry->next) - subTexEntry = subTexEntry->next; - - subTexEntry->next = - PLATFORM_MEM_ALLOC(arena, 1, AtlasSubTexture); - subTexEntry = subTexEntry->next; - } - - *subTexEntry = newSubTexEntry; - i32 keyLen = common_strlen(newSubTexEntry.key); - - subTexEntry->key = - PLATFORM_MEM_ALLOC(arena, keyLen + 1, char); - common_strncpy(subTexEntry->key, newSubTexEntry.key, - keyLen); - } - else - { -#ifdef DENGINE_DEBUG - DEBUG_LOG("Unsupported xml node name not parsed"); -#endif - } - - atlasChildNode = atlasChildNode->next; - } - } - else - { -#ifdef DENGINE_DEBUG - DEBUG_LOG("Unsupported xml node"); -#endif - } - } - else - { -#ifdef DENGINE_DEBUG - DEBUG_LOG("Unsupported xml node name not parsed"); -#endif - } - node = node->next; - } -} - -INTERNAL void recursiveFreeXmlTree(MemoryArena *arena, XmlNode *node) -{ - if (!node) - { - return; - } else - { - // NOTE(doyle): First attribute is statically allocated, only if there's - // more attributes do we dynamically allocate - XmlAttribute *attrib = node->attribute.next; - - while (attrib) - { - XmlAttribute *next = attrib->next; - - attrib->name = NULL; - attrib->value = NULL; - PLATFORM_MEM_FREE(arena, attrib, sizeof(XmlAttribute)); - attrib = next; - } - - recursiveFreeXmlTree(arena, node->child); - recursiveFreeXmlTree(arena, node->next); - - node->name = NULL; - node->isClosed = FALSE; - PLATFORM_MEM_FREE(arena, node, sizeof(XmlNode)); - } -} - -INTERNAL void freeXmlData(MemoryArena *arena, XmlToken *tokens, i32 numTokens, - XmlNode *tree) -{ - if (tree) recursiveFreeXmlTree(arena, tree); - if (tokens) PLATFORM_MEM_FREE(arena, tokens, numTokens * sizeof(XmlToken)); -} - INTERNAL void assetInit(GameState *state) { AssetManager *assetManager = &state->assetManager; @@ -656,40 +121,51 @@ INTERNAL void assetInit(GameState *state) Texture *tex = asset_getAndAllocFreeTexSlot(assetManager, arena, "nullTex"); *tex = texture_gen(1, 1, 4, CAST(u8 *)(&bitmap)); + //assetManager->texAtlas.type = hashtabletype_textureAtlas; + + /* + ********************************* + * Load terrain texture atlas data + ********************************* + */ PlatformFileRead terrainXml = {0}; i32 result = platform_readFileToBuffer( arena, "data/textures/WorldTraveller/terrain.xml", &terrainXml); - if (result) { DEBUG_LOG("Failed to read sprite sheet xml"); } else { - /* Tokenise buffer */ - i32 numTokens = 0; - XmlToken *xmlTokens = tokeniseXmlBuffer(arena, terrainXml.buffer, - terrainXml.size, &numTokens); + result = asset_loadXmlFile(assetManager, arena, &terrainXml); - /* Build XML tree from tokens */ - XmlNode *xmlTree = buildXmlTree(arena, xmlTokens, numTokens); + if (!result) + { + TexAtlas *terrainAtlas = + asset_getTexAtlas(assetManager, "terrain.png"); - /* Parse XML tree to game structures */ - parseXmlTreeToGame(assetManager, arena, xmlTree); + i32 numSubTextures = 1; + f32 duration = 1.0f; + char *grassTerrain[1] = {"grass.png"}; + asset_addAnimation(assetManager, arena, "terrainGrass", + terrainAtlas, grassTerrain, numSubTextures, + duration); + } + else + { +#ifdef DENGINE_DEBUG + DEBUG_LOG("Failed to load terrain sprite xml data"); +#endif + } - /* Free data */ - freeXmlData(arena, xmlTokens, numTokens, xmlTree); platform_closeFileRead(arena, &terrainXml); - - TexAtlas *terrainAtlas = asset_getTexAtlas(assetManager, "terrain.png"); - - i32 numSubTextures = 1; - f32 duration = 1.0f; - char *grassTerrain[1] = {"grass.png"}; - asset_addAnimation(assetManager, arena, "terrainGrass", terrainAtlas, - grassTerrain, numSubTextures, duration); } + /* + ********************************* + * Load Claude texture atlas data + ********************************* + */ PlatformFileRead claudeXml = {0}; result = platform_readFileToBuffer( arena, "data/textures/WorldTraveller/ClaudeSprite.xml", &claudeXml); @@ -700,74 +176,78 @@ INTERNAL void assetInit(GameState *state) } else { - /* Tokenise buffer */ - i32 numTokens = 0; - XmlToken *xmlTokens = tokeniseXmlBuffer(arena, claudeXml.buffer, - claudeXml.size, &numTokens); + result = asset_loadXmlFile(assetManager, arena, &claudeXml); - /* Build XML tree from tokens */ - XmlNode *xmlTree = buildXmlTree(arena, xmlTokens, numTokens); + if (!result) + { + TexAtlas *claudeAtlas = + asset_getTexAtlas(assetManager, "ClaudeSprite.png"); - /* Parse XML tree to game structures */ - parseXmlTreeToGame(assetManager, arena, xmlTree); + char *claudeIdle[1] = {"ClaudeSprite_Walk_Left_01"}; + f32 duration = 1.0f; + i32 numRects = ARRAY_COUNT(claudeIdle); + asset_addAnimation(assetManager, arena, "claudeIdle", claudeAtlas, + claudeIdle, numRects, duration); + + // Run animation + char *claudeRun[6] = { + "ClaudeSprite_Run_Left_01", "ClaudeSprite_Run_Left_02", + "ClaudeSprite_Run_Left_03", "ClaudeSprite_Run_Left_04", + "ClaudeSprite_Run_Left_05", "ClaudeSprite_Run_Left_06"}; + duration = 0.1f; + numRects = ARRAY_COUNT(claudeRun); + asset_addAnimation(assetManager, arena, "claudeRun", claudeAtlas, + claudeRun, numRects, duration); + + // Battle Idle animation + char *claudeBattleIdle[3] = {"ClaudeSprite_BattleIdle_Left_01", + "ClaudeSprite_BattleIdle_Left_02", + "ClaudeSprite_BattleIdle_Left_03"}; + numRects = ARRAY_COUNT(claudeBattleIdle); + duration = 0.2f; + asset_addAnimation(assetManager, arena, "claudeBattleIdle", + claudeAtlas, claudeBattleIdle, numRects, + duration); + + // Attack Left animation + char *claudeAttack[6] = { + "ClaudeSprite_Attack_Left_01", "ClaudeSprite_Attack_Left_02", + "ClaudeSprite_Attack_Left_03", "ClaudeSprite_Attack_Left_04", + "ClaudeSprite_Attack_Left_05", "ClaudeSprite_Attack_Left_06"}; + numRects = ARRAY_COUNT(claudeAttack); + duration = 0.1f; + asset_addAnimation(assetManager, arena, "claudeAttack", claudeAtlas, + claudeAttack, numRects, duration); + + // Victory animation + char *claudeVictory[8] = {"ClaudeSprite_Battle_Victory_01", + "ClaudeSprite_Battle_Victory_02", + "ClaudeSprite_Battle_Victory_03", + "ClaudeSprite_Battle_Victory_04", + "ClaudeSprite_Battle_Victory_05", + "ClaudeSprite_Battle_Victory_06", + "ClaudeSprite_Battle_Victory_07", + "ClaudeSprite_Battle_Victory_08"}; + numRects = ARRAY_COUNT(claudeVictory); + duration = 0.1f; + asset_addAnimation(assetManager, arena, "claudeVictory", + claudeAtlas, claudeVictory, numRects, duration); + + } + else + { +#ifdef DENGINE_DEBUG + DEBUG_LOG("Failed to load claude sprite xml data"); +#endif + } - /* Free data */ - freeXmlData(arena, xmlTokens, numTokens, xmlTree); platform_closeFileRead(arena, &claudeXml); + } - TexAtlas *claudeAtlas = - asset_getTexAtlas(assetManager, "ClaudeSprite.png"); - - char *claudeIdle[1] = {"ClaudeSprite_Walk_Left_01"}; - f32 duration = 1.0f; - i32 numRects = ARRAY_COUNT(claudeIdle); - asset_addAnimation(assetManager, arena, "claudeIdle", claudeAtlas, - claudeIdle, numRects, duration); - - // Run animation - char *claudeRun[6] = { - "ClaudeSprite_Run_Left_01", "ClaudeSprite_Run_Left_02", - "ClaudeSprite_Run_Left_03", "ClaudeSprite_Run_Left_04", - "ClaudeSprite_Run_Left_05", "ClaudeSprite_Run_Left_06"}; - duration = 0.1f; - numRects = ARRAY_COUNT(claudeRun); - asset_addAnimation(assetManager, arena, "claudeRun", claudeAtlas, - claudeRun, numRects, duration); - - // Battle Idle animation - char *claudeBattleIdle[3] = {"ClaudeSprite_BattleIdle_Left_01", - "ClaudeSprite_BattleIdle_Left_02", - "ClaudeSprite_BattleIdle_Left_03"}; - numRects = ARRAY_COUNT(claudeBattleIdle); - duration = 0.2f; - asset_addAnimation(assetManager, arena, "claudeBattleIdle", claudeAtlas, - claudeBattleIdle, numRects, duration); - - // Attack Left animation - char *claudeAttack[6] = { - "ClaudeSprite_Attack_Left_01", "ClaudeSprite_Attack_Left_02", - "ClaudeSprite_Attack_Left_03", "ClaudeSprite_Attack_Left_04", - "ClaudeSprite_Attack_Left_05", "ClaudeSprite_Attack_Left_06"}; - numRects = ARRAY_COUNT(claudeAttack); - duration = 0.1f; - asset_addAnimation(assetManager, arena, "claudeAttack", claudeAtlas, - claudeAttack, numRects, duration); - - // Victory animation - char *claudeVictory[8] = { - "ClaudeSprite_Battle_Victory_01", "ClaudeSprite_Battle_Victory_02", - "ClaudeSprite_Battle_Victory_03", "ClaudeSprite_Battle_Victory_04", - "ClaudeSprite_Battle_Victory_05", "ClaudeSprite_Battle_Victory_06", - "ClaudeSprite_Battle_Victory_07", "ClaudeSprite_Battle_Victory_08"}; - numRects = ARRAY_COUNT(claudeVictory); - duration = 0.1f; - asset_addAnimation(assetManager, arena, "claudeVictory", claudeAtlas, - claudeVictory, numRects, duration); #ifdef DENGINE_DEBUG - DEBUG_LOG("Animations created"); + DEBUG_LOG("Animations created"); #endif - } /* Load shaders */ asset_loadShaderFiles(assetManager, arena, "data/shaders/sprite.vert.glsl", @@ -981,30 +461,7 @@ INTERNAL void unitTest(MemoryArena *arena) ASSERT(common_atoi(" 32", common_strlen(" 32")) == -1); ASSERT(common_atoi("+32", common_strlen("+32")) == 32); ASSERT(common_atoi("+ 32", common_strlen("+ 32")) == 0); - - - PlatformFileRead xmlFileRead = {0}; - i32 result = platform_readFileToBuffer( - arena, "data/textures/WorldTraveller/ClaudeSprite.xml", &xmlFileRead); - if (result) - { - DEBUG_LOG( - "unitTest() error: Could not load XML file for memory free test"); - } - else - { - /* Tokenise buffer */ - i32 memBefore = arena->bytesAllocated; - i32 numTokens = 0; - XmlToken *xmlTokens = tokeniseXmlBuffer(arena, xmlFileRead.buffer, - xmlFileRead.size, &numTokens); - /* Build XML tree from tokens */ - XmlNode *xmlTree = buildXmlTree(arena, xmlTokens, numTokens); - freeXmlData(arena, xmlTokens, numTokens, xmlTree); - i32 memAfter = arena->bytesAllocated; - - ASSERT(memBefore == memAfter); - } + asset_unitTest(arena); } // TODO(doyle): Remove and implement own random generator! @@ -2172,7 +1629,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt) TexAtlas *heroAtlas = asset_getTexAtlas(assetManager, "ClaudeSprite.png"); Rect heroAvatarRect = - asset_getAtlasSubTexRect(heroAtlas, "ClaudeSprite_Avatar_01"); + asset_getSubTexRect(heroAtlas, "ClaudeSprite_Avatar_01"); v2 heroAvatarP = V2(10.0f, (renderer->size.h * 0.5f) - (0.5f * heroAvatarRect.size.h)); diff --git a/src/include/Dengine/AssetManager.h b/src/include/Dengine/AssetManager.h index 68f6072..36ca04a 100644 --- a/src/include/Dengine/AssetManager.h +++ b/src/include/Dengine/AssetManager.h @@ -7,59 +7,94 @@ /* Forward declaration */ typedef struct MemoryArena MemoryArena; +typedef struct PlatformFileRead PlatformFileRead; + +enum HashTableType +{ + hashtabletype_unknown, + hashtabletype_texture, + hashtabletype_textureAtlas, + hashtabletype_animation, + hashtabletype_count, +}; + +typedef struct HashTableEntry +{ + void *data; + char *key; + + void *next; +} HashTableEntry; + +typedef struct HashTable +{ + HashTableEntry *entries; + i32 size; + enum HashTableType type; +} HashTable; typedef struct AssetManager { + /* Hash Tables */ + TexAtlas texAtlas[8]; Texture textures[32]; - Shader shaders[32]; Animation anims[1024]; + + /* Primitive Array */ + Shader shaders[32]; AudioVorbis audio[32]; Font font; - - TexAtlas texAtlas[8]; } AssetManager; #define MAX_TEXTURE_SIZE 1024 -Rect asset_getAtlasSubTexRect(TexAtlas *atlas, char *key); - -AudioVorbis *asset_getVorbis(AssetManager *assetManager, - const enum AudioList type); - +/* + ********************************* + * Texture Asset Managing + ********************************* + */ +Rect asset_getSubTexRect(TexAtlas *atlas, char *key); Texture *asset_getTex(AssetManager *const assetManager, const char *const key); - TexAtlas *asset_makeTexAtlas(AssetManager *const assetManager, MemoryArena *arena, const char *const key); - -Shader *asset_getShader(AssetManager *assetManager, const enum ShaderList type); - TexAtlas *asset_getTexAtlas(AssetManager *const assetManager, const char *const key); - Texture *asset_getAndAllocFreeTexSlot(AssetManager *assetManager, MemoryArena *arena, const char *const key); - -Animation *asset_getAnim(AssetManager *assetManager, char *key); - -const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena *arena, - const char *const path, const enum AudioList type); const i32 asset_loadTextureImage(AssetManager *assetManager, MemoryArena *arena, const char *const path, const char *const key); +/* + ********************************* + * Animation Asset Managing + ********************************* + */ +void asset_addAnimation(AssetManager *assetManager, MemoryArena *arena, + char *animName, TexAtlas *atlas, char **subTextureNames, + i32 numSubTextures, f32 frameDuration); +Animation *asset_getAnim(AssetManager *assetManager, char *key); + +/* + ********************************* + * Everything else + ********************************* + */ +i32 asset_loadXmlFile(AssetManager *assetManager, MemoryArena *arena, + PlatformFileRead *fileRead); +AudioVorbis *asset_getVorbis(AssetManager *assetManager, + const enum AudioList type); +Shader *asset_getShader(AssetManager *assetManager, const enum ShaderList type); +const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena *arena, + const char *const path, const enum AudioList type); const i32 asset_loadShaderFiles(AssetManager *assetManager, MemoryArena *arena, const char *const vertexPath, const char *const fragmentPath, const enum ShaderList type); - const i32 asset_loadTTFont(AssetManager *assetManager, MemoryArena *arena, const char *filePath); - -void asset_addAnimation(AssetManager *assetManager, MemoryArena *arena, - char *animName, TexAtlas *atlas, char **subTextureNames, - i32 numSubTextures, f32 frameDuration); - v2 asset_stringDimInPixels(const Font *const font, const char *const string); +void asset_unitTest(MemoryArena *arena); #endif diff --git a/src/include/Dengine/Assets.h b/src/include/Dengine/Assets.h index 9d37359..55a16b9 100644 --- a/src/include/Dengine/Assets.h +++ b/src/include/Dengine/Assets.h @@ -10,6 +10,32 @@ /* Forward Declaration */ typedef struct Texture Texture; +typedef struct XmlAttribute +{ + b32 init; + char *name; + char *value; + + struct XmlAttribute *next; +} XmlAttribute; + +typedef struct XmlNode +{ + char *name; + XmlAttribute attribute; + + // NOTE(doyle): Track if node has more children + b32 isClosed; + + // NOTE(doyle): Direct child/parent + struct XmlNode *parent; + struct XmlNode *child; + + // NOTE(doyle): Else all other nodes + struct XmlNode *next; +} XmlNode; + + enum TexList { texlist_empty, diff --git a/src/include/Dengine/Debug.h b/src/include/Dengine/Debug.h index 5d5a742..c138242 100644 --- a/src/include/Dengine/Debug.h +++ b/src/include/Dengine/Debug.h @@ -18,6 +18,9 @@ enum DebugCallCount void debug_init(MemoryArena *arena, v2 windowSize, Font font); +#define DEBUG_RECURSIVE_PRINT_XML_TREE(sig) debug_recursivePrintXmlTree(sig, 1) +void debug_recursivePrintXmlTree(XmlNode *root, i32 levelsDeep); + void debug_callCountIncrement(enum DebugCallCount id); void debug_clearCallCounter(); #define DEBUG_LOG(string) debug_consoleLog(string, __FILE__, __LINE__);