From 4c73e381c3d7ccacbaa069e62bfb5e4e684e1682 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Thu, 8 Jan 2026 11:53:12 -0600 Subject: [PATCH] tree template variation on vector --- Engine/source/core/util/treeObject.cpp | 472 +++++++++++++++++++++++++ Engine/source/core/util/treeObject.h | 128 +++++++ 2 files changed, 600 insertions(+) create mode 100644 Engine/source/core/util/treeObject.cpp create mode 100644 Engine/source/core/util/treeObject.h diff --git a/Engine/source/core/util/treeObject.cpp b/Engine/source/core/util/treeObject.cpp new file mode 100644 index 000000000..4bc905092 --- /dev/null +++ b/Engine/source/core/util/treeObject.cpp @@ -0,0 +1,472 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- +#include "treeObject.h" +#include "console/engineAPI.h" + +IMPLEMENT_CONOBJECT(TreeObject); + +TreeObject::~TreeObject() +{ + if (mRoot != NULL) + _deleteNode(mRoot, false); + + mKeyMap.clear(); +} + +void TreeObject::initPersistFields() +{ + addProtectedField("treeType", TypeString, Offset(mTreeType, TreeObject), &_setType, &defaultProtectedGetFn, "Set type by name."); + Parent::initPersistFields(); +} + +void TreeObject::_internalSetType(const char* typeName) +{ + if (!typeName || !typeName[0]) return; + + ConsoleBaseType* newType = ConsoleBaseType::getTypeByName(typeName); + + if (newType) + mTreeType = newType; + else + Con::errorf("TreeObject: Invalid type '%s'. Use 'TypeS32', 'TypePoint3F', etc.", typeName); +} + +bool TreeObject::_setType(void* obj, const char* index, const char* data) +{ + TreeObject* tree = static_cast(obj); + if (tree != NULL) + tree->_internalSetType(data); + + return false; +} + +void TreeObject::setType(const char* typeName) +{ + if (typeName && typeName[0]) + { + ConsoleBaseType* newType = ConsoleBaseType::getTypeByName(typeName); + if (newType) + mTreeType = newType; + else + Con::errorf("TreeObject::setTreeType - Unknown console type '%s'! use listConsoleTypes for options", typeName); + } +} + +S32 TreeObject::addNode(S32 parentKey, const char* scriptData, S32 forcedKey) +{ + if (mTreeType == NULL) + { + Con::errorf("TreeObject::addNode - treeType not set. Cannot allocate node data."); + return -1; + } + + S32 finalKey = (forcedKey == -1) ? mNextFreeKey++ : forcedKey; + + if (forcedKey != -1) + { + if (findNode(finalKey)) + { + Con::errorf("TreeObject::addNode - Key collision! Key %d already exists.", finalKey); + return -1; + } + if (finalKey >= mNextFreeKey) + mNextFreeKey = finalKey + 1; + } + + void* newData = dMalloc(mTreeType->getTypeSize()); + dMemset(newData, 0, mTreeType->getTypeSize()); + + if (scriptData && scriptData[0]) + { + const char* scriptArgv[] = { scriptData }; + mTreeType->setData(newData, 1, scriptArgv, NULL, 0); + } + + Node* newNode = new Node(finalKey, mTreeType); + newNode->data = newData; + + Node* parent = findNode(parentKey); + if (parent) + { + parent->push_back(newNode); + newNode->parent = parent; + } + else + { + if (mRoot == NULL) + { + mRoot = newNode; + newNode->parent = NULL; + } + else + { + mRoot->push_back(newNode); + newNode->parent = mRoot; + } + } + + mKeyMap.insert(finalKey, newNode); + + return finalKey; +} + +void TreeObject::deleteNode(S32 key) +{ + Node* node = findNode(key); + if (node == NULL) + return; + + if (node->parent != NULL) + { + Node* parentNode = static_cast(node->parent); + for (S32 i = 0; i < parentNode->size(); i++) + { + if ((*parentNode)[i] == static_cast*>(node)) + { + parentNode->erase(i); + break; + } + } + } + if (node == mRoot) + mRoot = NULL; + _deleteNode(node, true); +} + +void TreeObject::_deleteNode(Node* node, bool unlinkFromMap) +{ + if (!node) return; + + for (U32 i = 0; i < node->size(); i++) + { + Node* child = static_cast((*node)[i]); + _deleteNode(child, unlinkFromMap); + } + + if (unlinkFromMap) { + mKeyMap.erase(node->key); + } + + if (node == mRoot) mRoot = NULL; + delete node; +} + +TreeObject::Node* TreeObject::findNode(S32 key) +{ + Map::Iterator iter = mKeyMap.find(key); + return (iter != mKeyMap.end()) ? iter->value : NULL; +} + +S32 TreeObject::getParent(S32 key) +{ + Node* n = findNode(key); + if (n == NULL || n->parent == NULL) + return -1; + return static_cast(n->getParentNode())->key; +} + +bool TreeObject::toParent(S32 key, S32 newParentKey) +{ + Node* targetNode = findNode(key); + Node* newParentNode = findNode(newParentKey); + + if (!targetNode || !newParentNode || key == newParentKey) + return false; + + Node* checkNode = newParentNode; + while (checkNode != NULL) + { + if (checkNode->key == key) + return false; + checkNode = static_cast(checkNode->parent); + } + if (targetNode->parent) + { + Tree* oldParent = targetNode->parent; + for (S32 i = 0; i < oldParent->size(); i++) { + if ((*oldParent)[i] == targetNode) { + oldParent->erase(i); + break; + } + } + } + + targetNode->parent = newParentNode; + newParentNode->push_back(targetNode); + + return true; +} + +Vector TreeObject::getChildren(S32 key) +{ + Vector keys; + Node* n = findNode(key); + if (n) + { + Vector*> children = n->getChildrenNodes(); + keys.reserve(children.size()); + for (U32 i = 0; i < children.size(); i++) + keys.push_back(static_cast(children[i])->key); + } + return keys; +} + +Vector TreeObject::getSiblings(S32 key) +{ + Vector keys; + Node* n = findNode(key); + if (n) + { + Vector*> siblings = n->getSiblingsNodes(); + keys.reserve(siblings.size()); + for (U32 i = 0; i < siblings.size(); i++) + keys.push_back(static_cast(siblings[i])->key); + } + return keys; +} + +const char* TreeObject::nodesToString(const Vector& keys) +{ + if (keys.empty()) + return ""; + + static const U32 bufSize = 1024; + char* returnBuffer = Con::getReturnBuffer(bufSize); + returnBuffer[0] = '\0'; + + bool first = true; + for (S32 i = 0; i < keys.size(); i++) + { + char keyBuf[16]; + dSprintf(keyBuf, sizeof(keyBuf), "%d", keys[i]); + + if (!first) + dStrcat(returnBuffer, " ", bufSize); + + dStrcat(returnBuffer, keyBuf, bufSize); + first = false; + } + + return returnBuffer; +} + +const char* TreeObject::toString() +{ + if (!mRoot || !mTreeType) return ""; + + const U32 bufSize = 16384; + char* heapBuffer = (char*)dMalloc(bufSize); + dMemset(heapBuffer, 0, bufSize); + + dSprintf(heapBuffer, bufSize, "%s\n", mTreeType->getTypeName()); + U32 bufferPos = dStrlen(heapBuffer); + + _toStringRecursive(mRoot, heapBuffer, bufferPos, bufSize); + heapBuffer[bufferPos] = '\0'; + + char* returnBuffer = Con::getReturnBuffer(bufSize); + dStrcpy(returnBuffer, heapBuffer, bufSize); + dFree(heapBuffer); + + return returnBuffer; +} + +void TreeObject::_toStringRecursive(Node* node, char* buffer, U32& bufferPos, U32 bufSize) +{ + if (!node || bufferPos >= (bufSize - 1)) return; + + char localDataStr[512] = ""; + if (node->data && node->type) { + const char* tempStr = node->type->getData(node->data, NULL, 0); + if (tempStr) { + dStrncpy(localDataStr, tempStr, sizeof(localDataStr) - 1); + localDataStr[sizeof(localDataStr) - 1] = '\0'; + } + } + + S32 pKey = (node->parent) ? static_cast(node->parent)->key : -1; + + char line[1024]; + dSprintf(line, sizeof(line), "%d %d \"%s\"\n", node->key, pKey, localDataStr); + U32 lineLen = dStrlen(line); + + if (bufferPos + lineLen < bufSize) { + dStrcpy(buffer + bufferPos, line, bufSize - bufferPos); + bufferPos += lineLen; + } + + for (U32 i = 0; i < node->size(); i++) { + Node* child = static_cast((*node)[i]); + _toStringRecursive(child, buffer, bufferPos, bufSize); + } +} + +void TreeObject::fromString(const char* data) +{ + if (!data || !*data) + return; + + if (mRoot) + _deleteNode(mRoot, true); + + mKeyMap.clear(); + mNextFreeKey = 0; + + const char* pipe = dStrchr(data, '\n'); + if (pipe) + { + char typeName[256]; + U32 typeLen = pipe - data; + if (typeLen > 1) + { + dStrncpy(typeName, data, typeLen); + typeName[typeLen] = '\0'; + setType(typeName); + } + _fromStringRecursive(pipe + 1, -1); + } +} + +void TreeObject::_fromStringRecursive(const char* str, S32 ignored) +{ + if (!str || !*str) return; + const char* p = str; + + while (*p) { + while (*p && (*p == '\n' || *p == '\r' || *p == ' ')) p++; + if (!*p) break; + + S32 key = 0; + S32 parentID = -1; + char dataBuf[1024] = ""; + + if (dSscanf(p, "%d %d \"%[^\"]\"", &key, &parentID, dataBuf) < 3) { + const char* nextLine = dStrchr(p, '\n'); + if (!nextLine) break; + p = nextLine + 1; + continue; + } + + addNode(parentID, dataBuf, key); + const char* nextLine = dStrchr(p, '\n'); + if (!nextLine) break; + p = nextLine + 1; + } +} + +//============================================================== +DefineEngineMethod(TreeObject, setTreeType, void, (const char* typeName), , + "@brief Sets the data type for the tree at runtime.\n" + "@param typeName The name of the type (e.g., 'Point3F', 'TransformF').") +{ + object->setType(typeName); +} + +DefineEngineMethod(TreeObject, addNode, S32, (S32 parentKey, const char* data, S32 forcedKey), (-1), + "@brief Adds a node to the hierarchy.\n" + "@param parentKey The ID of the parent (-1 for root).\n" + "@param data The data string (TransformF/Point3F).\n" + "@param forcedKey Optional S32 ID. If -1, one is generated.\n" + "@return The S32 key assigned to the node.") +{ + return object->addNode(parentKey, data, forcedKey); +} + +DefineEngineMethod(TreeObject, deleteNode, void, (S32 key), , + "Removes a node and its entire subtree from the armature using its S32 key.") +{ + object->deleteNode(key); +} + +DefineEngineMethod(TreeObject, getNode, const char*, (S32 key), , + "@brief Retrieves the data string stored in a specific node.\n" + "@param key The unique ID of the node.\n" + "@return The data string, or an empty string if the node/data is not found.") +{ + TreeObject::Node* node = object->findNode(key); + if (node && node->data && node->type) + { + const char* dataVal = node->type->getData(node->data, NULL, 0); + return dataVal ? dataVal : ""; + } + return ""; +} + +DefineEngineMethod(TreeObject, toParent, bool, (S32 key, S32 newParentKey), , + "@brief Shifts a node to a new parent in the hierarchy.\n" + "@param key The ID of the node to move.\n" + "@param newParentKey The ID of the new parent, or -1 for root.\n" + "@return True on success.") +{ + return object->toParent(key, newParentKey); +} + +DefineEngineMethod(TreeObject, getParent, S32, (S32 key), , "Returns parent key.") +{ + return object->getParent(key); +} + +DefineEngineMethod(TreeObject, getChildren, const char*, (S32 key), , "Returns space-separated child keys.") +{ + return object->nodesToString(object->getChildren(key)); +} + +DefineEngineMethod(TreeObject, getNumChildren, S32, (S32 key), , + "@brief Returns the number of children attached to the node identified by key.\n" + "@param key The S32 identifier of the node.") +{ + return object->getNumChildren(key); +} + +DefineEngineMethod(TreeObject, getSiblings, const char*, (S32 key), , "Returns space-separated sibling keys.") +{ + return object->nodesToString(object->getSiblings(key)); +} + +DefineEngineMethod(TreeObject, getNumSiblings, S32, (S32 key), , + "@brief Returns the number of siblings for the node identified by key.\n" + "@param key The S32 identifier of the node.") +{ + return object->getNumSiblings(key); +} + +DefineEngineMethod(TreeObject, toString, const char*, (), , + "Serializes the tree to a single string with a type header.") +{ + return object->toString(); +} + +DefineEngineMethod(TreeObject, fromString, void, (const char* data), , + "Rebuilds the tree from a serialized string.") +{ + object->fromString(data); +} + + +//============================================================== +DefineEngineFunction(listConsoleTypes, void, (), , "Lists all registered ConsoleBaseTypes.") +{ + for (ConsoleBaseType* type = ConsoleBaseType::getListHead(); type != NULL; type = type->getListNext()) + { + Con::printf("%s", type->getTypeName()); + } +} diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h new file mode 100644 index 000000000..13987671b --- /dev/null +++ b/Engine/source/core/util/treeObject.h @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "core/util/tVector.h" +#include "console/simObject.h" +#include "core/util/tDictionary.h" +#include "console/consoleTypes.h" +template +class Tree : public Vector*> { +public: + T data; + Tree* parent; + + Tree(const T& val = T(), Tree* p = NULL) : data(val), parent(p) {} + virtual ~Tree() { clear();} + + Tree* addChild(const T& val) { + Tree* child = new Tree(val, this); + push_back(child); + return child; + } + + void addChild(Tree* child) { + if (child) { + child->parent = this; + push_back(child); + } + } + + Tree* getParentNode() const { return parent; } + + Vector*> getChildrenNodes() const { + Vector*> children; + children.reserve(size()); + for (U32 i = 0; i < size(); i++) + children.push_back((*this)[i]); + return children; + } + + Vector*> getSiblingsNodes() const { + Vector*> siblings; + if (!parent) return siblings; + + siblings.reserve(parent->size() - 1); + for (U32 i = 0; i < parent->size(); i++) { + Tree* sibling = (*parent)[i]; + if (sibling != this) + siblings.push_back(sibling); + } + return siblings; + } +}; + +class TreeObject : public SimObject { + typedef SimObject Parent; + +public: + struct Node : public Tree { + S32 key; + ConsoleBaseType* type; + Node(S32 k, ConsoleBaseType* t) : Tree(NULL, NULL), key(k), type(t) {} + ~Node() { + if (data) + { + dFree(data); + data = NULL; + } + } + }; + + S32 mNextFreeKey; + Map mKeyMap; + Node* mRoot; + ConsoleBaseType* mTreeType; + + TreeObject() : mRoot(NULL), mNextFreeKey(0) + { + mTreeType = ConsoleBaseType::getType(TypeS32); + } + ~TreeObject(); + + void _internalSetType(const char* typeName); + static bool _setType(void* obj, const char* index, const char* data); + void setType(const char* typeName); + static void initPersistFields(); + + S32 addNode(S32 parentKey, const char* scriptData, S32 forcedKey = -1); + Node* findNode(S32 key); + void deleteNode(S32 key); + void _deleteNode(Node* node, bool unlinkFromMap); + + S32 getParent(S32 key); + bool toParent(S32 key, S32 newParentKey); + + Vector getChildren(S32 key); + S32 getNumChildren(S32 key) {return getChildren(key).size();}; + + Vector getSiblings(S32 key); + S32 getNumSiblings(S32 key) { return getSiblings(key).size(); }; + + const char* nodesToString(const Vector& keys); + const char* toString(); + void _toStringRecursive(Node* node, char* buffer, U32& bufferPos, U32 bufSize); + + void fromString(const char* data); + void _fromStringRecursive(const char* str, S32 parentKey); + + DECLARE_CONOBJECT(TreeObject); +};