From 4c73e381c3d7ccacbaa069e62bfb5e4e684e1682 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Thu, 8 Jan 2026 11:53:12 -0600 Subject: [PATCH 01/17] 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); +}; From b692aa14c2df298e86e79e60b102aa0da1bb69da Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sat, 24 Jan 2026 16:00:50 -0600 Subject: [PATCH 02/17] vectorobject for vector script exposure similar to the treeobject class. todo: see about using the former in the latter --- Engine/source/core/util/vectorObject.cpp | 347 +++++++++++++++++++++++ Engine/source/core/util/vectorObject.h | 63 ++++ 2 files changed, 410 insertions(+) create mode 100644 Engine/source/core/util/vectorObject.cpp create mode 100644 Engine/source/core/util/vectorObject.h diff --git a/Engine/source/core/util/vectorObject.cpp b/Engine/source/core/util/vectorObject.cpp new file mode 100644 index 000000000..ff97a5b0a --- /dev/null +++ b/Engine/source/core/util/vectorObject.cpp @@ -0,0 +1,347 @@ +//----------------------------------------------------------------------------- +// 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 "vectorObject.h" +#include "console/consoleTypes.h" +#include "console/engineAPI.h" + +IMPLEMENT_CONOBJECT(VectorObject); + +void VectorObject::initPersistFields() +{ + addProtectedField("varType", TypeString, Offset(mVarType, VectorObject), &_setType, &defaultProtectedGetFn, "Set type by name."); + Parent::initPersistFields(); +} + +void VectorObject::_internalSetType(const char* typeName) +{ + if (!typeName || !typeName[0]) return; + + ConsoleBaseType* newType = ConsoleBaseType::getTypeByName(typeName); + + if (newType) + mVarType = newType; + else + Con::errorf("VectorObject: Invalid type '%s'. Use 'TypeS32', 'TypePoint3F', etc.", typeName); +} + +bool VectorObject::_setType(void* obj, const char* index, const char* data) +{ + VectorObject* tree = static_cast(obj); + if (tree != NULL) + tree->_internalSetType(data); + + return false; +} + +void VectorObject::setType(const char* typeName) +{ + if (typeName && typeName[0]) + { + ConsoleBaseType* newType = ConsoleBaseType::getTypeByName(typeName); + if (newType) + mVarType = newType; + else + Con::errorf("VectorObject::setType - Unknown console type '%s'! use listConsoleTypes for options", typeName); + } +} + +void* VectorObject::get(U32 index) +{ + if (index >= mData.size()) + { + Con::errorf("VectorObject::get - Index %d out of bounds (size: %d)", index, mData.size()); + return NULL; + } + return mData[index]; +} + +void VectorObject::set(U32 index, void* data) +{ + if (index >= mData.size()) + { + Con::errorf("VectorObject::set - Index %d out of bounds (size: %d)", index, mData.size()); + return; + } + mData.set(data, index ); +} + +void VectorObject::push_back(void* data) +{ + mData.push_back(data); +} + +void VectorObject::insert(U32 index, void* data) +{ + if (index > mData.size()) + { + Con::errorf("VectorObject::insert - Index %d out of bounds (size: %d)", index, mData.size()); + return; + } + mData.insert(index, data); +} + +void VectorObject::erase(U32 index) +{ + if (index >= mData.size()) + { + Con::errorf("VectorObject::erase - Index %d out of bounds (size: %d)", index, mData.size()); + return; + } + mData.erase(index); +} + +void VectorObject::clear() +{ + mData.clear(); +} + +void VectorObject::fromString(char* dataString) +{ + clear(); + + if (!mVarType || !dataString || !dataString[0]) + return; + + String dataStr(dataString); + Vector tokens; + dataStr.split(" \t\n\r", tokens); + const U32 typeSize = 128; + for (U32 i = 0; i < tokens.size(); ++i) + { + const char* tokenStr = tokens[i].c_str(); + + void* nativeVar = mVarType->getNativeVariable(); + Con::setData(mVarType->getTypeID(), nativeVar, 0, 1, &tokenStr); + + void* persistentData = dMalloc(typeSize); + dMemcpy(persistentData, nativeVar, typeSize); + + mData.push_back(persistentData); + } +} + +char* VectorObject::toString() +{ + if (!mVarType || mData.empty()) + return ""; + + char* returnBuffer = Con::getReturnBuffer(16384); + returnBuffer[0] = '\0'; + + for (U32 i = 0; i < mData.size(); ++i) + { + const char* valString = Con::getData(mVarType->getTypeID(), mData[i], 0); + + if (!valString) continue; + + if (i > 0) + dStrcat(returnBuffer, " ", 16384); + + dStrcat(returnBuffer, valString, 16384); + } + + return returnBuffer; +} + +//--- ENGINE METHODS ---// +DefineEngineMethod(VectorObject, setType, void, (const char* typeName), , + "@brief Sets the data type for the vector at runtime.\n" + "@param typeName The name of the type (e.g., 'Point3F', 'TransformF').") +{ + object->setType(typeName); +} + +DefineEngineMethod(VectorObject, getType, const char*, (), , + "@brief Returns the name of the currently assigned ConsoleBaseType.\n" + "@return The type name as a string.") +{ + ConsoleBaseType* type = object->getVarType(); + return type ? type->getTypeName() : ""; +} + +DefineEngineMethod(VectorObject, size, S32, (), , + "@brief Returns the number of elements in the vector.\n" + "@return The number of elements.") +{ + return object->size(); +} + +DefineEngineMethod(VectorObject, clear, void, (), , + "@brief Clears all elements from the vector.") +{ + object->clear(); +} + +DefineEngineMethod(VectorObject, push_back, void, (const char* data), , + "@brief Appends an element to the end of the vector.\n" + "@param data The data string (TransformF/Point3F/etc).") +{ + if (!object->getVarType()) + { + Con::errorf("VectorObject::push_back - No variable type set!"); + return; + } + const char* dataStr = data; + void* nativeVar = object->getVarType()->getNativeVariable(); + Con::setData(object->getVarType()->getTypeID(), nativeVar, 0, 1, &dataStr); + const U32 typeSize = 128; + void* persistentData = dMalloc(typeSize); + dMemcpy(persistentData, nativeVar, typeSize); + object->push_back(persistentData); +} + +DefineEngineMethod(VectorObject, get, const char*, (S32 index), , + "@brief Retrieves an element from the vector.\n" + "@param index The index of the element to retrieve.\n" + "@return The data string (TransformF/Point3F/etc).") +{ + if (!object->getVarType()) + { + Con::errorf("VectorObject::get - No variable type set!"); + return ""; + } + void* data = object->get(index); + if (!data) + return ""; + return Con::getData(object->getVarType()->getTypeID(), data, 0); +} + +DefineEngineMethod(VectorObject, set, void, (S32 index, const char* data), , + "@brief Sets an element in the vector.\n" + "@param index The index of the element to set.\n" + "@param data The data string (TransformF/Point3F/etc).") +{ + if (!object->getVarType()) + { + Con::errorf("VectorObject::set - No variable type set!"); + return; + } + const char* dataStr = data; + void* nativeVar = object->getVarType()->getNativeVariable(); + Con::setData(object->getVarType()->getTypeID(), nativeVar, 0, 1, &dataStr); + const U32 typeSize = 128; + void* persistentData = dMalloc(typeSize); + dMemcpy(persistentData, nativeVar, typeSize); + object->set(index, persistentData); +} + +DefineEngineMethod(VectorObject, insert, void, (S32 index, const char* data), , + "@brief Inserts an element into the vector at the specified index.\n" + "@param index The index at which to insert the element.\n" + "@param data The data string (TransformF/Point3F/etc).") +{ + if (!object->getVarType()) + { + Con::errorf("VectorObject::insert - No variable type set!"); + return; + } + const char* dataStr = data; + void* nativeVar = object->getVarType()->getNativeVariable(); + Con::setData(object->getVarType()->getTypeID(), nativeVar, 0, 1, &dataStr); + const U32 typeSize = 128; + void* persistentData = dMalloc(typeSize); + dMemcpy(persistentData, nativeVar, typeSize); + object->insert(index, persistentData); +} + +DefineEngineMethod(VectorObject, erase, void, (S32 index), , + "@brief Removes an element from the vector at the specified index.\n" + "@param index The index of the element to remove.") +{ + object->erase(index); +} + +DefineEngineMethod(VectorObject, fromString, void, (const char* data), , + "@brief Populates the vector from a space-separated string of data.\n" + "@param data The space-separated data string.") +{ + char* dataCopy = dStrdup(data); + object->fromString(dataCopy); + dFree(dataCopy); +} + +DefineEngineMethod(VectorObject, toString, const char*, (), , + "@brief Converts the vector's contents to a space-separated string.\n" + "@return The space-separated data string.") +{ + return object->toString(); +} + +ConsoleType(TypeVector, TypeVector, VectorObject*, NULL) +ImplementConsoleTypeCasters(TypeVector, VectorObject*); + +ConsoleGetType(TypeVector) +{ + VectorObject* vObj = *((VectorObject**)dptr); + if (!vObj) return ""; + + // Returns "TypeName Element1 Element2 ..." + const char* typeName = vObj->getVarType() ? vObj->getVarType()->getTypeName() : "TypeVoid"; + + static const U32 bufSize = 16384; + char* returnBuffer = Con::getReturnBuffer(bufSize); + dSprintf(returnBuffer, bufSize, "%s %s", typeName, vObj->toString()); + + return returnBuffer; +} + +ConsoleSetType(TypeVector) +{ + if (argc < 1) return; + + VectorObject** vObjPtr = (VectorObject**)dptr; + VectorObject* vObj = *vObjPtr; + + if (vObj) + { + // Tokenize the input: The first entry is the TYPE + String dataStr(argv[0]); + Vector tokens; + dataStr.split(" \t\n\r", tokens); + + if (tokens.size() > 0) + { + // 1. Set the VarType from the first token (e.g., "TypePoint3F") + vObj->setType(tokens[0].c_str()); + + // 2. Process the rest as elements + if (tokens.size() > 1) + { + // Re-concatenate the remaining tokens into a data string + String remainingData = ""; + for (U32 i = 1; i < tokens.size(); ++i) + { + remainingData += tokens[i]; + if (i < tokens.size() - 1) remainingData += " "; + } + vObj->fromString(const_cast(remainingData.c_str())); + } + } + } + else + { + // If the field is currently null, attempt to resolve argv as an object name + if (!Sim::findObject(argv[0], *vObjPtr)) + Con::warnf("TypeVector::setData - Could not resolve VectorObject: %s", argv[0]); + } +} diff --git a/Engine/source/core/util/vectorObject.h b/Engine/source/core/util/vectorObject.h new file mode 100644 index 000000000..9db0e15e4 --- /dev/null +++ b/Engine/source/core/util/vectorObject.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- + +#ifndef _TVECTOROBJECT_H_ +#define _TVECTOROBJECT_H_ + +#include "console/simObject.h" + +class VectorObject : public SimObject { + typedef SimObject Parent; + +protected: + ConsoleBaseType* mVarType; + Vector mData; +public: + VectorObject(): mVarType(NULL) {}; + DECLARE_CONOBJECT(VectorObject); + + static void initPersistFields(); + + void _internalSetType(const char* typeName); + static bool _setType(void* obj, const char* index, const char* data); + void setType(const char* typeName); + ConsoleBaseType* getVarType() { return mVarType; }; + + U32 size() const { return mData.size(); }; + void* get(U32 index); + void set(U32 index, void* data); + void push_back(void* data); + void insert(U32 index, void* data); + void erase(U32 index); + void clear(); + void fromString(char* data); + char* toString(); +}; + +DefineConsoleType(TypeVector, VectorObject*); + +template +static inline S32 VecType() { + return TYPEID(); +} + +#endif From c72d8a1f9bdc0bdc7fc8740387861dd22d4ee283 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sun, 25 Jan 2026 13:18:01 -0600 Subject: [PATCH 03/17] baseline node augs tree->treenode for clarity, foreshorted a few method names, added a few inline utility methods --- Engine/source/core/util/treeObject.cpp | 10 ++-- Engine/source/core/util/treeObject.h | 65 +++++++++++++++++++------- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/Engine/source/core/util/treeObject.cpp b/Engine/source/core/util/treeObject.cpp index 4bc905092..18f89a212 100644 --- a/Engine/source/core/util/treeObject.cpp +++ b/Engine/source/core/util/treeObject.cpp @@ -140,7 +140,7 @@ void TreeObject::deleteNode(S32 key) Node* parentNode = static_cast(node->parent); for (S32 i = 0; i < parentNode->size(); i++) { - if ((*parentNode)[i] == static_cast*>(node)) + if ((*parentNode)[i] == static_cast*>(node)) { parentNode->erase(i); break; @@ -181,7 +181,7 @@ S32 TreeObject::getParent(S32 key) Node* n = findNode(key); if (n == NULL || n->parent == NULL) return -1; - return static_cast(n->getParentNode())->key; + return static_cast(n->getParent())->key; } bool TreeObject::toParent(S32 key, S32 newParentKey) @@ -201,7 +201,7 @@ bool TreeObject::toParent(S32 key, S32 newParentKey) } if (targetNode->parent) { - Tree* oldParent = targetNode->parent; + TreeNode* oldParent = targetNode->parent; for (S32 i = 0; i < oldParent->size(); i++) { if ((*oldParent)[i] == targetNode) { oldParent->erase(i); @@ -222,7 +222,7 @@ Vector TreeObject::getChildren(S32 key) Node* n = findNode(key); if (n) { - Vector*> children = n->getChildrenNodes(); + Vector*> children = n->getChildren(); keys.reserve(children.size()); for (U32 i = 0; i < children.size(); i++) keys.push_back(static_cast(children[i])->key); @@ -236,7 +236,7 @@ Vector TreeObject::getSiblings(S32 key) Node* n = findNode(key); if (n) { - Vector*> siblings = n->getSiblingsNodes(); + Vector*> siblings = n->getSiblings(); keys.reserve(siblings.size()); for (U32 i = 0; i < siblings.size(); i++) keys.push_back(static_cast(siblings[i])->key); diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index 13987671b..5723b0827 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -25,59 +25,90 @@ #include "core/util/tDictionary.h" #include "console/consoleTypes.h" template -class Tree : public Vector*> { +class TreeNode : public Vector*> { public: T data; - Tree* parent; + TreeNode* parent; + TreeNode(const T& val = T(), TreeNode* p = NULL) : data(val), parent(p){} + virtual ~TreeNode() { clear();} + //description logic + inline bool isRoot() const { return parent == NULL; } + inline bool isLeaf() const { return size() == 0; } - Tree(const T& val = T(), Tree* p = NULL) : data(val), parent(p) {} - virtual ~Tree() { clear();} + //parent logic + inline TreeNode* getParent() const { return parent; } + inline void setParent(TreeNode* p) + { + if (parent) parent->remove(this); + parent = p; + parent->push_back(this); + } - Tree* addChild(const T& val) { - Tree* child = new Tree(val, this); + //children logic + inline TreeNode* addChild(const T& val) { + TreeNode* child = new TreeNode(val, this); push_back(child); return child; } - - void addChild(Tree* child) { + inline void addChild(TreeNode* child) { if (child) { child->parent = this; push_back(child); } } + inline void addChildren(const Vector* children) { + if (!children) return; + for (U32 i = 0; i < children->size(); i++) { + TreeNode* child = new TreeNode((*children)[i], this); + push_back(child); + } + } + void operator =(Vector* other) { clear(); addChildren(other); } - Tree* getParentNode() const { return parent; } - - Vector*> getChildrenNodes() const { - Vector*> children; + inline U32 getNumChildren() const { return size(); } + inline bool hasChildren() const { return size() > 0; } + inline Vector*> getChildren() const { + Vector*> children; children.reserve(size()); for (U32 i = 0; i < size(); i++) children.push_back((*this)[i]); return children; } + inline void deleteChildren() + { + for (U32 i = 0; i < size(); i++) + delete (*this)[i]; + clear(); + } - Vector*> getSiblingsNodes() const { - Vector*> siblings; + //sibling logic + inline Vector*> getSiblings() const { + Vector*> siblings; if (!parent) return siblings; siblings.reserve(parent->size() - 1); for (U32 i = 0; i < parent->size(); i++) { - Tree* sibling = (*parent)[i]; + TreeNode* sibling = (*parent)[i]; if (sibling != this) siblings.push_back(sibling); } return siblings; } + + //data logic + inline T getData() const { return data; } + inline void setData(const T& val) { data = val; } + inline void operator =(const T& val) { data = val; } }; class TreeObject : public SimObject { typedef SimObject Parent; public: - struct Node : public Tree { + struct Node : public TreeNode { S32 key; ConsoleBaseType* type; - Node(S32 k, ConsoleBaseType* t) : Tree(NULL, NULL), key(k), type(t) {} + Node(S32 k, ConsoleBaseType* t) : TreeNode(NULL, NULL), key(k), type(t) {} ~Node() { if (data) { From 6d708af9ba167f736c2b1db770c49b97e0ae4ceb Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 26 Jan 2026 01:44:05 -0600 Subject: [PATCH 04/17] destructor work, set/get child by ID work --- Engine/source/core/util/treeObject.h | 87 +++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 8 deletions(-) diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index 5723b0827..7d3099be4 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -20,17 +20,29 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +#ifndef _TREEOBJECT_H_ +#define _TREEOBJECT_H_ #include "core/util/tVector.h" #include "console/simObject.h" #include "core/util/tDictionary.h" #include "console/consoleTypes.h" + template class TreeNode : public Vector*> { public: T data; TreeNode* parent; TreeNode(const T& val = T(), TreeNode* p = NULL) : data(val), parent(p){} - virtual ~TreeNode() { clear();} + virtual ~TreeNode() + { + deleteChildren(); + if (parent) + { + parent->remove(this); + parent = NULL; + } + } + //description logic inline bool isRoot() const { return parent == NULL; } inline bool isLeaf() const { return size() == 0; } @@ -45,29 +57,74 @@ public: } //children logic - inline TreeNode* addChild(const T& val) { + inline TreeNode* addChild(const T& val) + { TreeNode* child = new TreeNode(val, this); push_back(child); return child; } - inline void addChild(TreeNode* child) { + inline void addChild(TreeNode* child) + { if (child) { child->parent = this; push_back(child); } } - inline void addChildren(const Vector* children) { + inline void setChild(U32 i, TreeNode* child) + { + if (i >= size()) + { + U32 oldSize = size(); + increment((i + 1) - oldSize); + for (U32 j = oldSize; j < size(); j++) + (*this)[j] = NULL; + } + + if (child == NULL) + { + removeChild(i); + } + else + { + (*this)[i] = child; + child->parent = this; + } + } + + inline TreeNode* getChild(U32 i) const + { + if (!this) + return NULL; + if (i < size()) + return (*this)[i]; + return NULL; + } + + inline void addChildren(const Vector* children) + { if (!children) return; for (U32 i = 0; i < children->size(); i++) { TreeNode* child = new TreeNode((*children)[i], this); push_back(child); } } + + inline void removeChild(U32 i) + { + if (i < size()) + { + TreeNode* child = (*this)[i]; + if (child) + child->parent = NULL; + erase(i); + } + } void operator =(Vector* other) { clear(); addChildren(other); } inline U32 getNumChildren() const { return size(); } inline bool hasChildren() const { return size() > 0; } - inline Vector*> getChildren() const { + inline Vector*> getChildren() const + { Vector*> children; children.reserve(size()); for (U32 i = 0; i < size(); i++) @@ -76,13 +133,25 @@ public: } inline void deleteChildren() { - for (U32 i = 0; i < size(); i++) - delete (*this)[i]; + Vector*> children; + for (U32 i = 0; i < size(); i++) { + children.push_back((*this)[i]); + } clear(); + + for (U32 i = 0; i < children.size(); i++) + { + if (children[i]) + { + children[i]->parent = NULL; + delete children[i]; + } + } } //sibling logic - inline Vector*> getSiblings() const { + inline Vector*> getSiblings() const + { Vector*> siblings; if (!parent) return siblings; @@ -99,6 +168,7 @@ public: inline T getData() const { return data; } inline void setData(const T& val) { data = val; } inline void operator =(const T& val) { data = val; } + inline T* getDataPtr() { return &data; } }; class TreeObject : public SimObject { @@ -157,3 +227,4 @@ public: DECLARE_CONOBJECT(TreeObject); }; +#endif //_TREEOBJECT_H_ From 0175c109acbf4ff892b2d895a4f2b015bf3b8715 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Tue, 27 Jan 2026 00:32:12 -0600 Subject: [PATCH 05/17] safety --- Engine/source/core/util/treeObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index 7d3099be4..0fde074a3 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -53,7 +53,7 @@ public: { if (parent) parent->remove(this); parent = p; - parent->push_back(this); + if (parent) parent->push_back(this); } //children logic From 09f0fa2861d2e5dd2dfecdec277cfc80271a29f6 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Tue, 27 Jan 2026 17:47:19 -0600 Subject: [PATCH 06/17] cleanups and a few more utility methods --- Engine/source/core/util/treeObject.h | 188 ++++++++++++++++++++++++--- 1 file changed, 171 insertions(+), 17 deletions(-) diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index 0fde074a3..0ddd14a99 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -32,13 +32,20 @@ class TreeNode : public Vector*> { public: T data; TreeNode* parent; - TreeNode(const T& val = T(), TreeNode* p = NULL) : data(val), parent(p){} + TreeNode(const T& val = T(), TreeNode* p = NULL) : data(val), parent(p) {} virtual ~TreeNode() { deleteChildren(); if (parent) { - parent->remove(this); + for (U32 i = 0; i < parent->size(); i++) + { + if ((*parent)[i] == this) + { + parent->erase(i); + break; + } + } parent = NULL; } } @@ -46,12 +53,14 @@ public: //description logic inline bool isRoot() const { return parent == NULL; } inline bool isLeaf() const { return size() == 0; } - - //parent logic + // Parent logic inline TreeNode* getParent() const { return parent; } + inline void setParent(TreeNode* p) { - if (parent) parent->remove(this); + if (parent == p) return; + + if (parent) parent->removeChild(this); parent = p; if (parent) parent->push_back(this); } @@ -65,10 +74,21 @@ public: } inline void addChild(TreeNode* child) { - if (child) { - child->parent = this; - push_back(child); + if (!child) return; + + // Check if already a child to prevent duplicates + for (U32 i = 0; i < size(); i++) + { + if ((*this)[i] == child) + return; // Already a child } + + // Remove from old parent if needed + if (child->parent && child->parent != this) + child->parent->removeChild(child); + + child->parent = this; + push_back(child); } inline void setChild(U32 i, TreeNode* child) { @@ -80,12 +100,41 @@ public: (*this)[j] = NULL; } + TreeNode* oldChild = (*this)[i]; + if (oldChild == child) return; + + // Unlink old child + if (oldChild) + { + oldChild->parent = NULL; + } + if (child == NULL) { - removeChild(i); + (*this)[i] = NULL; } else { + // Handle moving from different parent vs same parent + if (child->parent && child->parent != this) + { + child->parent->removeChild(child); + } + else if (child->parent == this) + { + // Moving within same parent - find and remove from old slot + for (U32 j = 0; j < size(); j++) + { + if (j != i && (*this)[j] == child) + { + erase(j); + if (j < i) + i--; + break; + } + } + } + (*this)[i] = child; child->parent = this; } @@ -93,8 +142,6 @@ public: inline TreeNode* getChild(U32 i) const { - if (!this) - return NULL; if (i < size()) return (*this)[i]; return NULL; @@ -103,12 +150,28 @@ public: inline void addChildren(const Vector* children) { if (!children) return; - for (U32 i = 0; i < children->size(); i++) { + for (U32 i = 0; i < children->size(); i++) + { TreeNode* child = new TreeNode((*children)[i], this); push_back(child); } } + inline void removeChild(TreeNode* child) + { + if (!child) return; + + for (U32 i = 0; i < size(); i++) + { + if ((*this)[i] == child) + { + child->parent = NULL; + erase(i); + return; + } + } + } + inline void removeChild(U32 i) { if (i < size()) @@ -119,6 +182,62 @@ public: erase(i); } } + inline void replaceChild(TreeNode* oldChild, TreeNode* newChild) + { + if (!oldChild) return; + + for (U32 i = 0; i < size(); i++) + { + if ((*this)[i] == oldChild) + { + oldChild->parent = NULL; + + if (newChild) + { + // Handle moving from different parent vs same parent + if (newChild->parent && newChild->parent != this) + { + newChild->parent->removeChild(newChild); + } + else if (newChild->parent == this) + { + // Moving within same parent - find and remove from old slot + for (U32 j = 0; j < size(); j++) + { + if (j != i && (*this)[j] == newChild) + { + erase(j); + if (j < i) + i--; + break; + } + } + } + + (*this)[i] = newChild; + newChild->parent = this; + } + else + { + (*this)[i] = NULL; + } + return; + } + } + } + + inline void compact() + { + // Remove NULL entries from children array + for (U32 i = 0; i < size(); ) + { + if ((*this)[i] == NULL) + erase(i); + else + i++; + } + } + void operator =(Vector* other) { clear(); addChildren(other); } inline U32 getNumChildren() const { return size(); } @@ -128,13 +247,18 @@ public: Vector*> children; children.reserve(size()); for (U32 i = 0; i < size(); i++) - children.push_back((*this)[i]); + { + if ((*this)[i]) // Skip NULLs + children.push_back((*this)[i]); + } return children; } + inline void deleteChildren() { Vector*> children; - for (U32 i = 0; i < size(); i++) { + for (U32 i = 0; i < size(); i++) + { children.push_back((*this)[i]); } clear(); @@ -156,9 +280,10 @@ public: if (!parent) return siblings; siblings.reserve(parent->size() - 1); - for (U32 i = 0; i < parent->size(); i++) { + for (U32 i = 0; i < parent->size(); i++) + { TreeNode* sibling = (*parent)[i]; - if (sibling != this) + if (sibling && sibling != this) // Skip NULLs siblings.push_back(sibling); } return siblings; @@ -169,6 +294,35 @@ public: inline void setData(const T& val) { data = val; } inline void operator =(const T& val) { data = val; } inline T* getDataPtr() { return &data; } + + // Traversal helpers + inline U32 getDepth() const + { + U32 depth = 0; + TreeNode* current = parent; + while (current) + { + depth++; + current = current->parent; + } + return depth; + } + + inline TreeNode* getRoot() + { + TreeNode* current = this; + while (current->parent) + current = current->parent; + return current; + } + + inline const TreeNode* getRoot() const + { + const TreeNode* current = this; + while (current->parent) + current = current->parent; + return current; + } }; class TreeObject : public SimObject { @@ -213,7 +367,7 @@ public: bool toParent(S32 key, S32 newParentKey); Vector getChildren(S32 key); - S32 getNumChildren(S32 key) {return getChildren(key).size();}; + S32 getNumChildren(S32 key) { return getChildren(key).size(); }; Vector getSiblings(S32 key); S32 getNumSiblings(S32 key) { return getSiblings(key).size(); }; From 8ec65267ea2871f7abe5885e7c847b7ce70573e5 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Fri, 30 Jan 2026 00:52:33 -0600 Subject: [PATCH 07/17] cleanups, utility methods extended template to use a compiletime nullClears (defaults to true) to clear nodes if they would be set to NULL entries cleanups and refactors yet more utility methods --- Engine/source/core/util/treeObject.cpp | 82 +-- Engine/source/core/util/treeObject.h | 731 +++++++++++++++++++------ 2 files changed, 622 insertions(+), 191 deletions(-) diff --git a/Engine/source/core/util/treeObject.cpp b/Engine/source/core/util/treeObject.cpp index 18f89a212..259a9338e 100644 --- a/Engine/source/core/util/treeObject.cpp +++ b/Engine/source/core/util/treeObject.cpp @@ -26,9 +26,20 @@ IMPLEMENT_CONOBJECT(TreeObject); TreeObject::~TreeObject() { - if (mRoot != NULL) - _deleteNode(mRoot, false); - + if (mRoot) + { + delete mRoot; + mRoot = NULL; + } + + // Clean up any orphaned nodes (safety measure) + for (Map::Iterator i = mKeyMap.begin(); i != mKeyMap.end(); ++i) + { + if (i->value && i->value->parent == NULL && i->value != mRoot) + { + delete i->value; + } + } mKeyMap.clear(); } @@ -132,42 +143,46 @@ S32 TreeObject::addNode(S32 parentKey, const char* scriptData, S32 forcedKey) void TreeObject::deleteNode(S32 key) { Node* node = findNode(key); - if (node == NULL) + if (!node) 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; + if (!node) + return; - for (U32 i = 0; i < node->size(); i++) + // Collect all keys in subtree for map cleanup + Vector keysToRemove; + if (unlinkFromMap) { - Node* child = static_cast((*node)[i]); - _deleteNode(child, unlinkFromMap); + node->forEachInSubtree([&keysToRemove](TreeNode* n) { + Node* scriptNode = static_cast(n); + keysToRemove.push_back(scriptNode->key); + }); } - - if (unlinkFromMap) { - mKeyMap.erase(node->key); - } - - if (node == mRoot) mRoot = NULL; + + // Remove from parent's children array + node->nullParent(); + + // Delete the node (destructor handles children recursively) delete node; + + // Clean up map entries + if (unlinkFromMap) + { + for (U32 i = 0; i < keysToRemove.size(); i++) + { + mKeyMap.erase(keysToRemove[i]); + } + } } TreeObject::Node* TreeObject::findNode(S32 key) @@ -192,6 +207,7 @@ bool TreeObject::toParent(S32 key, S32 newParentKey) if (!targetNode || !newParentNode || key == newParentKey) return false; + // Check for circular reference (prevent making ancestor a child) Node* checkNode = newParentNode; while (checkNode != NULL) { @@ -199,17 +215,9 @@ bool TreeObject::toParent(S32 key, S32 newParentKey) return false; checkNode = static_cast(checkNode->parent); } - if (targetNode->parent) - { - TreeNode* oldParent = targetNode->parent; - for (S32 i = 0; i < oldParent->size(); i++) { - if ((*oldParent)[i] == targetNode) { - oldParent->erase(i); - break; - } - } - } - + + // Remove from old parent and add to new parent + targetNode->nullParent(); targetNode->parent = newParentNode; newParentNode->push_back(targetNode); diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index 0ddd14a99..abd634481 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -27,239 +27,533 @@ #include "core/util/tDictionary.h" #include "console/consoleTypes.h" -template -class TreeNode : public Vector*> { +// Add nullClears as a template argument (default: true. set false for BVH/quad/octrees) +template +class TreeNode : public Vector*> { public: T data; - TreeNode* parent; - TreeNode(const T& val = T(), TreeNode* p = NULL) : data(val), parent(p) {} + TreeNode* parent; + TreeNode(const T& val = T(), TreeNode* p = NULL) : data(val), parent(p) {} + TreeNode(const Vector*>& children, TreeNode* p = NULL) + : data(T()), parent(p) + { + this->increment(children.size()); + for (U32 i = 0; i < children.size(); ++i) { + (*this)[i] = children[i]; + if (children[i]) + children[i]->parent = this; + } + } virtual ~TreeNode() { deleteChildren(); - if (parent) - { - for (U32 i = 0; i < parent->size(); i++) - { - if ((*parent)[i] == this) - { - parent->erase(i); - break; - } - } - parent = NULL; - } + parent = NULL; } //description logic inline bool isRoot() const { return parent == NULL; } - inline bool isLeaf() const { return size() == 0; } + inline bool isLeaf(bool checkNULLs = true) const { + if (!checkNULLs) + return this->size() == 0; + for (U32 i = 0; i < this->size(); ++i) { + if ((*this)[i] != NULL) + return false; + } + return true; + } // Parent logic - inline TreeNode* getParent() const { return parent; } + inline TreeNode* getParent() const { return parent; } - inline void setParent(TreeNode* p) + inline void setParent(TreeNode* p) { if (parent == p) return; if (parent) parent->removeChild(this); parent = p; - if (parent) parent->push_back(this); + if (parent) parent->push_back_unique(this); } //children logic - inline TreeNode* addChild(const T& val) + inline TreeNode* addChild(const T& val) { - TreeNode* child = new TreeNode(val, this); - push_back(child); + TreeNode* child = new TreeNode(val, this); + this->push_back_unique(child); return child; } - inline void addChild(TreeNode* child) - { - if (!child) return; - // Check if already a child to prevent duplicates - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i] == child) - return; // Already a child + inline void addChild(TreeNode* child) + { + // Prevent duplicate child + for (U32 i = 0; i < this->size(); i++) + AssertFatal((*this)[i] != child, "TreeNode::addChild - Attempted to add duplicate child"); + +#ifdef TORQUE_DEBUG + // Prevent cycles + TreeNode* ancestor = this; + while (ancestor) { + AssertFatal(ancestor != child, "TreeNode::addChild - Would create circular parent relationship"); + ancestor = ancestor->parent; + } +#endif + + // If child has a parent and it's not this, remove from old parent + if (child->parent && child->parent != this) { + child->parent->removeChild(child); // Remove from old parent } - // Remove from old parent if needed - if (child->parent && child->parent != this) - child->parent->removeChild(child); - child->parent = this; - push_back(child); + this->push_back_unique(child); } - inline void setChild(U32 i, TreeNode* child) + + inline void setChild(U32 i, TreeNode* child) { - if (i >= size()) + if (i >= this->size()) { - U32 oldSize = size(); - increment((i + 1) - oldSize); - for (U32 j = oldSize; j < size(); j++) + U32 oldSize = this->size(); + this->increment((i + 1) - oldSize); + for (U32 j = oldSize; j < this->size(); j++) (*this)[j] = NULL; } - TreeNode* oldChild = (*this)[i]; + TreeNode* oldChild = (*this)[i]; if (oldChild == child) return; // Unlink old child if (oldChild) - { oldChild->parent = NULL; - } + // If forcing to NULL, just clear the slot and return if (child == NULL) { - (*this)[i] = NULL; + if constexpr (nullClears) + this->erase(i); + else + (*this)[i] = NULL; + return; } - else - { - // Handle moving from different parent vs same parent - if (child->parent && child->parent != this) - { - child->parent->removeChild(child); - } - else if (child->parent == this) - { - // Moving within same parent - find and remove from old slot - for (U32 j = 0; j < size(); j++) - { - if (j != i && (*this)[j] == child) - { - erase(j); - if (j < i) - i--; - break; - } - } - } - (*this)[i] = child; - child->parent = this; +#ifdef TORQUE_DEBUG + // Prevent cycles + TreeNode* ancestor = this; + while (ancestor) { + AssertFatal(ancestor != child, "TreeNode::setChild - Would create circular parent relationship"); + ancestor = ancestor->parent; } +#endif + + // Remove from old parent if different + if (child->parent && child->parent != this) + { + child->parent->removeChild(child); + } + else if (child->parent == this) + { + // Moving within same parent, nullify other references + for (U32 j = 0; j < this->size(); j++) + { + if (j != i && (*this)[j] == child) + if constexpr (nullClears) + this->erase(j); + else + (*this)[j] = NULL; + } + } + + // Prevent duplicate child + for (U32 j = 0; j < this->size(); j++) + AssertFatal(j == i || (*this)[j] != child, "TreeNode::setChild - Attempted to set duplicate child"); + + (*this)[i] = child; + child->parent = this; } - inline TreeNode* getChild(U32 i) const + inline TreeNode* getChild(U32 i) const { - if (i < size()) + if (i < this->size()) + { + // Defensive: validate pointer before returning + if ((*this)[i] && (*this)[i]->parent != this) + { + AssertFatal(false, "TreeNode::getChild - Child parent pointer mismatch"); + } return (*this)[i]; + } return NULL; } - inline void addChildren(const Vector* children) + + inline void addChildren(const Vector*>* children) { if (!children) return; for (U32 i = 0; i < children->size(); i++) { - TreeNode* child = new TreeNode((*children)[i], this); - push_back(child); + TreeNode* child = (*children)[i]; + + // Prevent duplicate child + for (U32 j = 0; j < this->size(); j++) + AssertFatal((*this)[j] != child, "TreeNode::addChildren - Attempted to add duplicate child"); + +#ifdef TORQUE_DEBUG + // Prevent cycles + TreeNode* ancestor = this; + while (ancestor) { + AssertFatal(ancestor != child, "TreeNode::addChildren - Would create circular parent relationship"); + ancestor = ancestor->parent; + } +#endif + + // If child has a parent and it's not this, remove from old parent + if (child->parent && child->parent != this) { + child->parent->removeChild(child); + } + + child->parent = this; + this->push_back_unique(child); } } - inline void removeChild(TreeNode* child) + inline void nullParent(bool orphan = true) { - if (!child) return; + // Null the parent pointer with optional cleanup + // @param orphan If true (default), properly removes from parent's children array first. + // If false, only nulls parent pointer (use during manual tree destruction). + if (orphan && parent) + { + parent->removeChild(this); + } + else + { + parent = NULL; + } + } - for (U32 i = 0; i < size(); i++) + inline bool isValidTree() const + { + // Validate tree structure - check parent-child relationships + for (U32 i = 0; i < this->size(); i++) + { + TreeNode* child = (*this)[i]; + if (child && child->parent != this) + return false; // Child doesn't point back to parent + } + return true; + } + + inline bool hasChild(TreeNode* child) const + { + if (!child) return false; + for (U32 i = 0; i < this->size(); i++) + { + if ((*this)[i] == child) + return true; + } + return false; + } + + inline S32 getChildIndex(TreeNode* child) const + { + if (!child) return -1; + for (U32 i = 0; i < this->size(); i++) + { + if ((*this)[i] == child) + return (S32)i; + } + return -1; + } + + inline void clearChildren() + { + // Remove all children without deleting them + for (U32 i = 0; i < this->size(); i++) + { + TreeNode* child = (*this)[i]; + if (child) + { + // Harden: ensure child is actually parented to this node + if (child->parent != this) + { +#ifdef TORQUE_DEBUG + Con::warnf("TreeNode::clearChildren - Child at index %u has incorrect parent pointer (corruption detected)", i); +#endif + // Detach anyway to avoid dangling pointer + child->parent = NULL; + } + else + { + child->parent = NULL; + } + } + } + clear(); + } + + inline void swapChildren(U32 i, U32 j) + { + if (i >= this->size() || j >= this->size() || i == j) + return; + + TreeNode* childI = (*this)[i]; + TreeNode* childJ = (*this)[j]; + + // Update parent pointers if needed + if (childI && childI->parent != this) + childI->parent = this; + if (childJ && childJ->parent != this) + childJ->parent = this; + + // Swap the pointers + (*this)[i] = childJ; + (*this)[j] = childI; + } + + inline TreeNode* findChild(const T& val) + { + for (U32 i = 0; i < this->size(); i++) + { + if ((*this)[i] && (*this)[i]->data == val) + return (*this)[i]; + } + return NULL; + } + + inline const TreeNode* findChild(const T& val) const + { + for (U32 i = 0; i < this->size(); i++) + { + if ((*this)[i] && (*this)[i]->data == val) + return (*this)[i]; + } + return NULL; + } + + template + inline void forEachChild(Func callback) + { + for (U32 i = 0; i < this->size(); i++) + { + if ((*this)[i]) + callback((*this)[i]); + } + } + + template + inline void forEachInSubtree(Func callback) + { + // Pre-order traversal: process node, then children + callback(this); + for (U32 i = 0; i < this->size(); i++) + { + if ((*this)[i]) + (*this)[i]->forEachInSubtree(callback); + } + } + template + inline void forEachLeaf(Func callback, bool checkNULLs = true) + { + if (isLeaf(checkNULLs)) + { + callback(this); + } + else + { + for (U32 i = 0; i < this->size(); i++) + { + if ((*this)[i]) + (*this)[i]->forEachLeaf(callback, checkNULLs); + } + } + } + + inline void findLeaves(Vector*>& outLeaves, bool checkNULLs = true) + { + forEachLeaf([&outLeaves](TreeNode* leaf) { + outLeaves.push_back_unique(leaf); + }, checkNULLs); + } + inline U32 getTreeSize() const + { + U32 count = 1; // Count this node + for (U32 i = 0; i < this->size(); i++) + { + if ((*this)[i]) + count += (*this)[i]->getTreeSize(); + } + return count; + } + + inline U32 getMaxDepth() const + { + if (isLeaf()) return 0; + + U32 maxDepth = 0; + for (U32 i = 0; i < this->size(); i++) + { + if ((*this)[i]) + { + U32 childDepth = (*this)[i]->getMaxDepth(); + if (childDepth > maxDepth) + maxDepth = childDepth; + } + } + return maxDepth + 1; + } + + inline void removeChild(TreeNode* child) + { + AssertFatal(child != NULL, "TreeNode::removeChild - Attempted to remove NULL child"); + bool found = false; + for (U32 i = 0; i < this->size(); i++) { if ((*this)[i] == child) { child->parent = NULL; - erase(i); - return; + if constexpr (nullClears) + this->erase(i); + else + (*this)[i] = NULL; + found = true; + break; } } + if (!found) + { +#ifdef TORQUE_DEBUG + // Defensive: attempt to repair if possible + for (U32 i = 0; i < this->size(); i++) + { + if ((*this)[i] && (*this)[i]->parent == this) + { + // Child pointer mismatch, but parent pointer matches: repair + (*this)[i]->parent = NULL; + if constexpr (nullClears) + this->erase(i); + else + (*this)[i] = NULL; + Con::warnf("TreeNode::removeChild - Repaired mismatched child pointer at index %u", i); + found = true; + break; + } + } + if (!found) + Con::errorf("TreeNode::removeChild - Child not found in parent's children array (corruption detected)"); +#endif + } + AssertFatal(found, "TreeNode::removeChild - Child not found in parent's children array"); } inline void removeChild(U32 i) { - if (i < size()) - { - TreeNode* child = (*this)[i]; - if (child) - child->parent = NULL; - erase(i); - } + AssertFatal(i < this->size(), "TreeNode::removeChild - Index out of bounds"); + TreeNode* child = (*this)[i]; + if (child) + child->parent = NULL; + if constexpr (nullClears) + this->erase(i); + else + (*this)[i] = NULL; } - inline void replaceChild(TreeNode* oldChild, TreeNode* newChild) + + inline void replaceChild(TreeNode* oldChild, TreeNode* newChild) { - if (!oldChild) return; + AssertFatal(oldChild != NULL, "TreeNode::replaceChild - Attempted to replace NULL child"); - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i] == oldChild) - { - oldChild->parent = NULL; + S32 idx = getChildIndex(oldChild); + AssertFatal(idx != -1, "TreeNode::replaceChild - oldChild not found in children"); - if (newChild) - { - // Handle moving from different parent vs same parent - if (newChild->parent && newChild->parent != this) - { - newChild->parent->removeChild(newChild); - } - else if (newChild->parent == this) - { - // Moving within same parent - find and remove from old slot - for (U32 j = 0; j < size(); j++) - { - if (j != i && (*this)[j] == newChild) - { - erase(j); - if (j < i) - i--; - break; - } - } - } - - (*this)[i] = newChild; - newChild->parent = this; - } - else - { - (*this)[i] = NULL; - } - return; - } - } + // Use hardened setChild for all safety checks and pointer management + setChild((U32)idx, newChild); } inline void compact() { - // Remove NULL entries from children array - for (U32 i = 0; i < size(); ) + for (U32 i = 0; i < this->size(); ) { - if ((*this)[i] == NULL) - erase(i); - else + TreeNode* child = (*this)[i]; + if constexpr (nullClears) + { + // Compact style: erase NULL slots, keep only valid children. + if (child == NULL) + { + this->erase(i); + continue; + } + // Repair parent pointer if needed. + if (child->parent != this) + child->parent = this; i++; + } + else + { + // BVH/slot-stable style: leave NULLs, just repair parent pointers. + if (child && child->parent != this) + child->parent = this; + i++; + } } } - void operator =(Vector* other) { clear(); addChildren(other); } - - inline U32 getNumChildren() const { return size(); } - inline bool hasChildren() const { return size() > 0; } - inline Vector*> getChildren() const + inline bool hasChildren() const { - Vector*> children; - children.reserve(size()); - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i]) // Skip NULLs - children.push_back((*this)[i]); + if constexpr (nullClears) + return this->size() > 0; + else { + for (U32 i = 0; i < this->size(); ++i) + if ((*this)[i]) return true; + return false; + } + } + + inline U32 getNumChildren() const { + if constexpr (nullClears) + return this->size(); + else { + U32 count = 0; + for (U32 i = 0; i < this->size(); ++i) + if ((*this)[i]) ++count; + return count; + } + } + + inline Vector*> getChildren() const + { + Vector*> children; + if constexpr (nullClears) { + // All slots are valid children + children = *this; + } + else { + // Only non-NULL slots are valid children + children.reserve(this->size()); + for (U32 i = 0; i < this->size(); ++i) + if ((*this)[i]) + children.push_back_unique((*this)[i]); } return children; } inline void deleteChildren() { - Vector*> children; - for (U32 i = 0; i < size(); i++) + Vector*> children; + for (U32 i = 0; i < this->size(); i++) { - children.push_back((*this)[i]); + TreeNode* child = (*this)[i]; + if (child) + { + // Harden: ensure child is actually parented to this node + if (child->parent != this) + { +#ifdef TORQUE_DEBUG + Con::warnf("TreeNode::deleteChildren - Child at index %u has incorrect parent pointer (corruption detected)", i); +#endif + // Detach anyway to avoid dangling pointer + child->parent = NULL; + } + else + { + child->parent = NULL; + } + } + children.push_back_unique(child); } clear(); @@ -267,24 +561,50 @@ public: { if (children[i]) { - children[i]->parent = NULL; + // Child should already be detached delete children[i]; } } } - //sibling logic - inline Vector*> getSiblings() const + inline void collectSubtree(Vector*>& nodes) { - Vector*> siblings; + // Pre-order collection of all nodes in subtree + forEachInSubtree([&nodes](TreeNode* n) { + nodes.push_back_unique(n); + }); + } + + inline void collectSubtreeData(Vector& dataList) + { + // Collect just the data from subtree + forEachInSubtree([&dataList](TreeNode* n) { + dataList.push_back_unique(n->data); + }); + } + + void collectAncestors(Vector*>& outAncestors) const + { + TreeNode* node = parent; + while (node) + { + outAncestors.push_back_unique(node); + node = node->parent; + } + } + + //sibling logic + inline Vector*> getSiblings() const + { + Vector*> siblings; if (!parent) return siblings; siblings.reserve(parent->size() - 1); for (U32 i = 0; i < parent->size(); i++) { - TreeNode* sibling = (*parent)[i]; + TreeNode* sibling = (*parent)[i]; if (sibling && sibling != this) // Skip NULLs - siblings.push_back(sibling); + siblings.push_back_unique(sibling); } return siblings; } @@ -299,7 +619,7 @@ public: inline U32 getDepth() const { U32 depth = 0; - TreeNode* current = parent; + TreeNode* current = parent; while (current) { depth++; @@ -308,21 +628,124 @@ public: return depth; } - inline TreeNode* getRoot() + inline TreeNode* getRoot() { - TreeNode* current = this; + TreeNode* current = this; while (current->parent) current = current->parent; return current; } - inline const TreeNode* getRoot() const + inline const TreeNode* getRoot() const { - const TreeNode* current = this; + const TreeNode* current = this; while (current->parent) current = current->parent; return current; } + + /// Returns true if this node is present in the given vector (used for cycle detection). + bool isVisited(const Vector*>& visited) const + { + for (U32 i = 0; i < visited.size(); ++i) + { + if (visited[i] == this) + return true; + } + return false; + } + + // Gather up to maxCandidates leaves or grandchildren for rotation from a node. + static U32 gatherRotationCandidates(TreeNode* parent, Vector*>& outNodes, U32 maxCandidates = 8) + { + outNodes.clear(); + for (U32 i = 0; i < parent->size() && outNodes.size() < maxCandidates; ++i) { + TreeNode* child = (*parent)[i]; + if (!child) continue; + if (child->isLeaf()) { + outNodes.push_back_unique(child); + } + else { + for (U32 j = 0; j < child->size() && outNodes.size() < maxCandidates; ++j) { + TreeNode* grandChild = (*child)[j]; + if (grandChild) outNodes.push_back_unique(grandChild); + } + } + } + return outNodes.size(); + } + + // Finds the best way to pair/group N nodes into two groups of groupSize each, minimizing cost. + // CostFunc: F32 (*costFunc)(const TreeNode*, const TreeNode*) + static bool findBestRotationPairing( + const Vector*>& nodes, + U32 groupSize, + F32(*costFunc)(const TreeNode*, const TreeNode*), + Vector& outGroupA, + Vector& outGroupB, + F32& outBestCost) + { + const U32 n = nodes.size(); + if (n < 2 * groupSize) return false; + + outBestCost = F32_MAX; + bool found = false; + + Vector indices; + indices.setSize(n); + for (U32 i = 0; i < n; ++i) indices[i] = i; + + Vector groupA, groupB; + groupA.setSize(groupSize); + groupB.setSize(groupSize); + + while (true) { + for (U32 i = 0; i < groupSize; ++i) + groupA[i] = indices[i]; + U32 bIdx = 0; + for (U32 i = groupSize; i < n; ++i) + groupB[bIdx++] = indices[i]; + if (bIdx == groupSize) { + F32 costA = costFunc(nodes[groupA[0]], nodes[groupA[1]]); + F32 costB = costFunc(nodes[groupB[0]], nodes[groupB[1]]); + F32 totalCost = costA + costB; + if (totalCost < outBestCost) { + outBestCost = totalCost; + outGroupA = groupA; + outGroupB = groupB; + found = true; + } + } + + S32 k = n - 2; + while (k >= 0 && indices[k] >= indices[k + 1]) --k; + if (k < 0) break; + S32 l = n - 1; + while (indices[k] >= indices[l]) --l; + U32 tmp = indices[k]; indices[k] = indices[l]; indices[l] = tmp; + for (U32 i = k + 1, j = n - 1; i < j; ++i, --j) { + tmp = indices[i]; indices[i] = indices[j]; indices[j] = tmp; + } + } + return found; + } + + //converters + + template + Derived* getChildAs(U32 i) { return static_cast(getChild(i)); } + template + const Derived* getChildAs(U32 i) const { return static_cast(getChild(i)); } + + template + Vector getChildrenAs() const + { + Vector out; + for (U32 i = 0; i < this->size(); ++i) + if ((*this)[i]) + out.push_back(static_cast((*this)[i])); + return out; + } }; class TreeObject : public SimObject { From 1df49dc6f726a58f88636bd28991eace8d6ce206 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 2 Feb 2026 22:11:23 -0600 Subject: [PATCH 08/17] general cleanups, hardening. added new methods inline void nullChild(U32 i) static void safeDetachFromParent(TreeNode* node) inline void forEachLeafAs(Func callback) --- Engine/source/core/util/treeObject.cpp | 12 +- Engine/source/core/util/treeObject.h | 202 +++++++++++++++++-------- 2 files changed, 145 insertions(+), 69 deletions(-) diff --git a/Engine/source/core/util/treeObject.cpp b/Engine/source/core/util/treeObject.cpp index 259a9338e..7aca2cfa4 100644 --- a/Engine/source/core/util/treeObject.cpp +++ b/Engine/source/core/util/treeObject.cpp @@ -169,9 +169,14 @@ void TreeObject::_deleteNode(Node* node, bool unlinkFromMap) }); } + // Remove from parent's children array node->nullParent(); - + + // If deleting the root node, clear mRoot + if (node == mRoot) + mRoot = NULL; + // Delete the node (destructor handles children recursively) delete node; @@ -333,8 +338,11 @@ void TreeObject::fromString(const char* data) if (!data || !*data) return; - if (mRoot) + + if (mRoot) { _deleteNode(mRoot, true); + mRoot = NULL; + } mKeyMap.clear(); mNextFreeKey = 0; diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index abd634481..aa3f3d146 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -33,11 +33,11 @@ class TreeNode : public Vector*> { public: T data; TreeNode* parent; - TreeNode(const T& val = T(), TreeNode* p = NULL) : data(val), parent(p) {} + TreeNode(const T& val = T(), TreeNode* p = NULL) : data(val), parent(p){} TreeNode(const Vector*>& children, TreeNode* p = NULL) : data(T()), parent(p) { - this->increment(children.size()); + increment(children.size()); for (U32 i = 0; i < children.size(); ++i) { (*this)[i] = children[i]; if (children[i]) @@ -52,14 +52,16 @@ public: //description logic inline bool isRoot() const { return parent == NULL; } - inline bool isLeaf(bool checkNULLs = true) const { - if (!checkNULLs) - return this->size() == 0; - for (U32 i = 0; i < this->size(); ++i) { - if ((*this)[i] != NULL) - return false; + inline bool isLeaf() const { + if constexpr (nullClears) + return size() == 0; + else { + for (U32 i = 0; i < size(); ++i) { + if ((*this)[i] != NULL) + return false; + } + return true; } - return true; } // Parent logic inline TreeNode* getParent() const { return parent; } @@ -77,14 +79,14 @@ public: inline TreeNode* addChild(const T& val) { TreeNode* child = new TreeNode(val, this); - this->push_back_unique(child); + push_back_unique(child); return child; } inline void addChild(TreeNode* child) { // Prevent duplicate child - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) AssertFatal((*this)[i] != child, "TreeNode::addChild - Attempted to add duplicate child"); #ifdef TORQUE_DEBUG @@ -102,16 +104,16 @@ public: } child->parent = this; - this->push_back_unique(child); + push_back_unique(child); } inline void setChild(U32 i, TreeNode* child) { - if (i >= this->size()) + if (i >= size()) { - U32 oldSize = this->size(); - this->increment((i + 1) - oldSize); - for (U32 j = oldSize; j < this->size(); j++) + U32 oldSize = size(); + increment((i + 1) - oldSize); + for (U32 j = oldSize; j < size(); j++) (*this)[j] = NULL; } @@ -126,7 +128,7 @@ public: if (child == NULL) { if constexpr (nullClears) - this->erase(i); + erase(i); else (*this)[i] = NULL; return; @@ -149,18 +151,18 @@ public: else if (child->parent == this) { // Moving within same parent, nullify other references - for (U32 j = 0; j < this->size(); j++) + for (U32 j = 0; j < size(); j++) { if (j != i && (*this)[j] == child) if constexpr (nullClears) - this->erase(j); + erase(j); else (*this)[j] = NULL; } } // Prevent duplicate child - for (U32 j = 0; j < this->size(); j++) + for (U32 j = 0; j < size(); j++) AssertFatal(j == i || (*this)[j] != child, "TreeNode::setChild - Attempted to set duplicate child"); (*this)[i] = child; @@ -169,7 +171,7 @@ public: inline TreeNode* getChild(U32 i) const { - if (i < this->size()) + if (i < size()) { // Defensive: validate pointer before returning if ((*this)[i] && (*this)[i]->parent != this) @@ -188,27 +190,57 @@ public: for (U32 i = 0; i < children->size(); i++) { TreeNode* child = (*children)[i]; + if (!child) + continue; // Skip null children // Prevent duplicate child - for (U32 j = 0; j < this->size(); j++) - AssertFatal((*this)[j] != child, "TreeNode::addChildren - Attempted to add duplicate child"); + bool isDuplicate = false; + for (U32 j = 0; j < size(); j++) + { + if ((*this)[j] == child) + { + isDuplicate = true; + break; + } + } + if (isDuplicate) + continue; #ifdef TORQUE_DEBUG // Prevent cycles TreeNode* ancestor = this; - while (ancestor) { + while (ancestor) + { AssertFatal(ancestor != child, "TreeNode::addChildren - Would create circular parent relationship"); ancestor = ancestor->parent; } #endif // If child has a parent and it's not this, remove from old parent - if (child->parent && child->parent != this) { - child->parent->removeChild(child); + if (child->parent && child->parent != this) + { + // Defensive: only remove if the parent still points to this child + bool foundInParent = false; + for (U32 k = 0; k < child->parent->size(); ++k) + { + if ((*child->parent)[k] == child) + { + foundInParent = true; + break; + } + } + if (foundInParent) + child->parent->removeChild(child); + else + child->parent = NULL; } + // Harden: skip if child is already correctly parented + if (child->parent == this) + continue; + child->parent = this; - this->push_back_unique(child); + push_back_unique(child); } } @@ -218,19 +250,26 @@ public: // @param orphan If true (default), properly removes from parent's children array first. // If false, only nulls parent pointer (use during manual tree destruction). if (orphan && parent) - { parent->removeChild(this); - } - else - { - parent = NULL; - } + parent = NULL; } +/// Sets the child pointer at index i to NULL and clears its parent pointer. +/// Does not erase the slot, regardless of nullClears. +inline void nullChild(U32 i) +{ + if (i >= size()) + return; + TreeNode* child = (*this)[i]; + if (child && child->parent == this) + child->parent = NULL; + (*this)[i] = NULL; +} + inline bool isValidTree() const { // Validate tree structure - check parent-child relationships - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { TreeNode* child = (*this)[i]; if (child && child->parent != this) @@ -242,7 +281,7 @@ public: inline bool hasChild(TreeNode* child) const { if (!child) return false; - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { if ((*this)[i] == child) return true; @@ -253,7 +292,7 @@ public: inline S32 getChildIndex(TreeNode* child) const { if (!child) return -1; - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { if ((*this)[i] == child) return (S32)i; @@ -264,7 +303,7 @@ public: inline void clearChildren() { // Remove all children without deleting them - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { TreeNode* child = (*this)[i]; if (child) @@ -289,7 +328,7 @@ public: inline void swapChildren(U32 i, U32 j) { - if (i >= this->size() || j >= this->size() || i == j) + if (i >= size() || j >= size() || i == j) return; TreeNode* childI = (*this)[i]; @@ -308,7 +347,7 @@ public: inline TreeNode* findChild(const T& val) { - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { if ((*this)[i] && (*this)[i]->data == val) return (*this)[i]; @@ -318,7 +357,7 @@ public: inline const TreeNode* findChild(const T& val) const { - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { if ((*this)[i] && (*this)[i]->data == val) return (*this)[i]; @@ -329,7 +368,7 @@ public: template inline void forEachChild(Func callback) { - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { if ((*this)[i]) callback((*this)[i]); @@ -341,39 +380,39 @@ public: { // Pre-order traversal: process node, then children callback(this); - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { if ((*this)[i]) (*this)[i]->forEachInSubtree(callback); } } template - inline void forEachLeaf(Func callback, bool checkNULLs = true) + inline void forEachLeaf(Func callback) { - if (isLeaf(checkNULLs)) + if (isLeaf()) { callback(this); } else { - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { if ((*this)[i]) - (*this)[i]->forEachLeaf(callback, checkNULLs); + (*this)[i]->forEachLeaf(callback); } } } - inline void findLeaves(Vector*>& outLeaves, bool checkNULLs = true) + inline void findLeaves(Vector*>& outLeaves) { forEachLeaf([&outLeaves](TreeNode* leaf) { outLeaves.push_back_unique(leaf); - }, checkNULLs); + }); } inline U32 getTreeSize() const { U32 count = 1; // Count this node - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { if ((*this)[i]) count += (*this)[i]->getTreeSize(); @@ -386,7 +425,7 @@ public: if (isLeaf()) return 0; U32 maxDepth = 0; - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { if ((*this)[i]) { @@ -398,17 +437,35 @@ public: return maxDepth + 1; } + static void safeDetachFromParent(TreeNode* node) + { + if (!node) return; + if (node->parent) + { + // Only remove if the parent still points to this node + for (U32 i = 0; i < node->parent->size(); ++i) + { + if ((*node->parent)[i] == node) + { + node->parent->removeChild(node); + break; + } + } + } + node->parent = NULL; + } + inline void removeChild(TreeNode* child) { AssertFatal(child != NULL, "TreeNode::removeChild - Attempted to remove NULL child"); bool found = false; - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { if ((*this)[i] == child) { child->parent = NULL; if constexpr (nullClears) - this->erase(i); + erase(i); else (*this)[i] = NULL; found = true; @@ -419,14 +476,14 @@ public: { #ifdef TORQUE_DEBUG // Defensive: attempt to repair if possible - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { if ((*this)[i] && (*this)[i]->parent == this) { // Child pointer mismatch, but parent pointer matches: repair (*this)[i]->parent = NULL; if constexpr (nullClears) - this->erase(i); + erase(i); else (*this)[i] = NULL; Con::warnf("TreeNode::removeChild - Repaired mismatched child pointer at index %u", i); @@ -443,12 +500,12 @@ public: inline void removeChild(U32 i) { - AssertFatal(i < this->size(), "TreeNode::removeChild - Index out of bounds"); + AssertFatal(i < size(), "TreeNode::removeChild - Index out of bounds"); TreeNode* child = (*this)[i]; if (child) child->parent = NULL; if constexpr (nullClears) - this->erase(i); + erase(i); else (*this)[i] = NULL; } @@ -466,7 +523,7 @@ public: inline void compact() { - for (U32 i = 0; i < this->size(); ) + for (U32 i = 0; i < size(); ) { TreeNode* child = (*this)[i]; if constexpr (nullClears) @@ -474,7 +531,7 @@ public: // Compact style: erase NULL slots, keep only valid children. if (child == NULL) { - this->erase(i); + erase(i); continue; } // Repair parent pointer if needed. @@ -495,9 +552,9 @@ public: inline bool hasChildren() const { if constexpr (nullClears) - return this->size() > 0; + return size() > 0; else { - for (U32 i = 0; i < this->size(); ++i) + for (U32 i = 0; i < size(); ++i) if ((*this)[i]) return true; return false; } @@ -505,10 +562,10 @@ public: inline U32 getNumChildren() const { if constexpr (nullClears) - return this->size(); + return size(); else { U32 count = 0; - for (U32 i = 0; i < this->size(); ++i) + for (U32 i = 0; i < size(); ++i) if ((*this)[i]) ++count; return count; } @@ -523,8 +580,8 @@ public: } else { // Only non-NULL slots are valid children - children.reserve(this->size()); - for (U32 i = 0; i < this->size(); ++i) + children.reserve(size()); + for (U32 i = 0; i < size(); ++i) if ((*this)[i]) children.push_back_unique((*this)[i]); } @@ -534,12 +591,11 @@ public: inline void deleteChildren() { Vector*> children; - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < size(); i++) { TreeNode* child = (*this)[i]; if (child) { - // Harden: ensure child is actually parented to this node if (child->parent != this) { #ifdef TORQUE_DEBUG @@ -741,11 +797,23 @@ public: Vector getChildrenAs() const { Vector out; - for (U32 i = 0; i < this->size(); ++i) + for (U32 i = 0; i < size(); ++i) if ((*this)[i]) out.push_back(static_cast((*this)[i])); return out; } + + template + inline void forEachLeafAs(Func callback) + { + if (isLeaf()) + callback(static_cast(this)); + else + for (U32 i = 0; i < size(); i++) + if ((*this)[i]) + (*this)[i]->template forEachLeafAs(callback); + } + }; class TreeObject : public SimObject { From 39124f6408ad5e2ad8b152d12194dbd10bb785e8 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Tue, 3 Feb 2026 18:41:19 -0600 Subject: [PATCH 09/17] sorted methods into aspects for ease of lookup, , const correctness handling, more utility methods, mostly focused on extracting vectors, plugging in callback enabled functions, and the ability to add safety checks like circular dependency detection (also tree stitching) --- Engine/source/core/util/treeObject.h | 1026 +++++++++++++------------- 1 file changed, 530 insertions(+), 496 deletions(-) diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index aa3f3d146..ab721ed60 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -31,26 +31,112 @@ template class TreeNode : public Vector*> { public: + //------------------------------------------------------------------------- + // Data Members + //------------------------------------------------------------------------- T data; TreeNode* parent; - TreeNode(const T& val = T(), TreeNode* p = NULL) : data(val), parent(p){} + + //------------------------------------------------------------------------- + // Constructors / Destructor + //------------------------------------------------------------------------- + TreeNode(const T& val = T(), TreeNode* p = NULL) + : data(val), parent(p) {} + TreeNode(const Vector*>& children, TreeNode* p = NULL) : data(T()), parent(p) { increment(children.size()); for (U32 i = 0; i < children.size(); ++i) { (*this)[i] = children[i]; - if (children[i]) + if (children[i]) { + AssertFatal(children[i] != this, "TreeNode::Constructor - Circular parent relationship"); children[i]->parent = this; + } } } + virtual ~TreeNode() { deleteChildren(); parent = NULL; } + //------------------------------------------------------------------------- + // Deep Copy / Cloning + //------------------------------------------------------------------------- + // Creates a deep copy of this node and its entire subtree. + TreeNode* clone() const { + auto* copy = new TreeNode(data, NULL); + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) + copy->addChild((*this)[i]->clone()); + return copy; + } - //description logic + template + TreeNode* cloneIf(Func pred) const { + if (!pred(this)) return NULL; + auto* newNode = new TreeNode(data, NULL); + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) { + auto* filteredChild = (*this)[i]->cloneIf(pred); + if (filteredChild) + newNode->addChild(filteredChild); + } + return newNode; + } + + bool equals(const TreeNode* other) const { + if (!other || data != other->data || size() != other->size()) + return false; + for (U32 i = 0; i < size(); ++i) { + if ((*this)[i] && other->getChild(i)) { + if (!(*this)[i]->equals(other->getChild(i))) + return false; + } + else if ((*this)[i] || other->getChild(i)) { + return false; + } + } + return true; + } + // Stitches 'subtree' as a child of 'atNode' (re-parents, does not clone). + static TreeNode* mergeAt(TreeNode* atNode, TreeNode* subtree) { + if (!atNode || !subtree) + return NULL; + atNode->addChild(subtree); + return subtree; + } + + // Clones 'subtree' and attaches the clone as a child of 'atNode'. + static TreeNode* cloneAt(TreeNode* atNode, const TreeNode* subtree) { + if (!atNode || !subtree) + return NULL; + TreeNode* newClone = subtree->clone(); + atNode->addChild(newClone); + return newClone; + } + bool isSubtreeOf(const TreeNode* other) const { + if (!other) return false; + if (equals(other)) return true; + for (U32 i = 0; i < other->size(); ++i) + if ((*other)[i] && isSubtreeOf((*other)[i])) + return true; + return false; + } + // Assignment operator: shallow copy of data and parent pointer only. + // Does NOT copy children or subtree. + TreeNode& operator=(const TreeNode& other) { + if (this == &other) + return *this; + data = other.data; + parent = other.parent; + // Children are NOT copied; this is a shallow assignment. + return *this; + } + //------------------------------------------------------------------------- + // Node Type & Relationship Queries + //------------------------------------------------------------------------- inline bool isRoot() const { return parent == NULL; } inline bool isLeaf() const { if constexpr (nullClears) @@ -63,96 +149,230 @@ public: return true; } } - // Parent logic - inline TreeNode* getParent() const { return parent; } - - inline void setParent(TreeNode* p) - { + inline bool hasChildren() const { + if constexpr (nullClears) + return size() > 0; + else { + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) return true; + return false; + } + } + inline U32 getNumChildren() const { + if constexpr (nullClears) + return size(); + else { + U32 count = 0; + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) ++count; + return count; + } + } + inline bool hasChild(TreeNode* child) const { + if (!child) return false; + for (U32 i = 0; i < size(); i++) + if ((*this)[i] == child) + return true; + return false; + } + inline S32 getChildIndex(TreeNode* child) const { + if (!child) return -1; + for (U32 i = 0; i < size(); i++) + if ((*this)[i] == child) + return (S32)i; + return -1; + } + inline bool isValidTree() const { + for (U32 i = 0; i < size(); i++) { + TreeNode* child = (*this)[i]; + if (child && child->parent != this) + return false; + } + return true; + } + bool isVisited(const Vector*>& visited) const { + for (U32 i = 0; i < visited.size(); ++i) + if (visited[i] == this) + return true; + return false; + } + template + TreeNode* findLeafIf(Func pred) { + if (isLeaf() && pred(this)) return this; + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) { + auto* found = (*this)[i]->findLeafIf(pred); + if (found) return found; + } + return NULL; + } + //------------------------------------------------------------------------- + // Parent/Ancestor Logic + //------------------------------------------------------------------------- + inline TreeNode* getParent() { return parent; } + inline const TreeNode* getParent() const { return parent; } + inline TreeNode* getRoot() { + TreeNode* current = this; + while (current->parent) + current = current->parent; + return current; + } + inline const TreeNode* getRoot() const { + const TreeNode* current = this; + while (current->parent) + current = current->parent; + return current; + } + bool isAncestorOf(const TreeNode* other) const { + const TreeNode* node = other->parent; + while (node) { + if (node == this) return true; + node = node->parent; + } + return false; + } + bool isDescendantOf(const TreeNode* other) const { + return other->isAncestorOf(this); + } + TreeNode* lowestCommonAncestor(TreeNode* other) { + Vector*> pathA, pathB; + getPathToRoot(pathA); + other->getPathToRoot(pathB); + TreeNode* lca = NULL; + S32 i = pathA.size() - 1, j = pathB.size() - 1; + while (i >= 0 && j >= 0 && pathA[i] == pathB[j]) { + lca = pathA[i]; + --i; --j; + } + return lca; + } + void getPathToRoot(Vector*>& out) const { + const TreeNode* node = this; + while (node) { + out.push_back(node); + node = node->parent; + } + } + inline void setParent(TreeNode* p) { if (parent == p) return; - if (parent) parent->removeChild(this); parent = p; if (parent) parent->push_back_unique(this); } - - //children logic - inline TreeNode* addChild(const T& val) - { + inline void nullParent(bool orphan = true) { + if (orphan && parent) + parent->removeChild(this); + parent = NULL; + } + static void safeDetachFromParent(TreeNode* node) { + if (!node) return; + if (node->parent) { + for (U32 i = 0; i < node->parent->size(); ++i) { + if ((*node->parent)[i] == node) { + node->parent->removeChild(node); + break; + } + } + } + node->parent = NULL; + } + void collectAncestors(Vector*>& outAncestors) const { + TreeNode* node = parent; + while (node) { + outAncestors.push_back_unique(node); + node = node->parent; + } + } + void collectAncestorsSortedByDepth(const Vector*>& leaves, Vector*>& outAncestors) const { + Map*, bool> ancestorSet; + for (U32 i = 0; i < leaves.size(); ++i) { + TreeNode* node = leaves[i]->getParent(); + while (node) { + ancestorSet.insert(node, true); + node = node->getParent(); + } + } + outAncestors.clear(); + outAncestors.reserve(ancestorSet.size()); + for (typename Map*, bool>::Iterator iter = ancestorSet.begin(); iter != ancestorSet.end(); ++iter) + outAncestors.push_back(iter->key); + outAncestors.sort([](TreeNode* a, TreeNode* b) { + U32 depthA = a->getDepth(); + U32 depthB = b->getDepth(); + return (depthA > depthB) ? -1 : (depthA < depthB) ? 1 : 0; + }); + } + inline U32 getDepth() const { + U32 depth = 0; + TreeNode* current = parent; + while (current) { + depth++; + current = current->parent; + } + return depth; + } + template + void forEachAncestor(Func callback) const { + const TreeNode* node = parent; + while (node) { + callback(node); + node = node->parent; + } + } + //------------------------------------------------------------------------- + // Child Logic + //------------------------------------------------------------------------- + inline TreeNode* addChild(const T& val) { TreeNode* child = new TreeNode(val, this); push_back_unique(child); return child; } - - inline void addChild(TreeNode* child) - { - // Prevent duplicate child + inline void addChild(TreeNode* child) { for (U32 i = 0; i < size(); i++) AssertFatal((*this)[i] != child, "TreeNode::addChild - Attempted to add duplicate child"); - #ifdef TORQUE_DEBUG - // Prevent cycles TreeNode* ancestor = this; while (ancestor) { AssertFatal(ancestor != child, "TreeNode::addChild - Would create circular parent relationship"); ancestor = ancestor->parent; } #endif - - // If child has a parent and it's not this, remove from old parent if (child->parent && child->parent != this) { - child->parent->removeChild(child); // Remove from old parent + child->parent->removeChild(child); } - child->parent = this; push_back_unique(child); } - - inline void setChild(U32 i, TreeNode* child) - { - if (i >= size()) - { + inline void setChild(U32 i, TreeNode* child) { + if (i >= size()) { U32 oldSize = size(); increment((i + 1) - oldSize); for (U32 j = oldSize; j < size(); j++) (*this)[j] = NULL; } - TreeNode* oldChild = (*this)[i]; if (oldChild == child) return; - - // Unlink old child if (oldChild) oldChild->parent = NULL; - - // If forcing to NULL, just clear the slot and return - if (child == NULL) - { + if (child == NULL) { if constexpr (nullClears) erase(i); else (*this)[i] = NULL; return; } - #ifdef TORQUE_DEBUG - // Prevent cycles TreeNode* ancestor = this; while (ancestor) { AssertFatal(ancestor != child, "TreeNode::setChild - Would create circular parent relationship"); ancestor = ancestor->parent; } #endif - - // Remove from old parent if different - if (child->parent && child->parent != this) - { + if (child->parent && child->parent != this) { child->parent->removeChild(child); } - else if (child->parent == this) - { - // Moving within same parent, nullify other references - for (U32 j = 0; j < size(); j++) - { + else if (child->parent == this) { + for (U32 j = 0; j < size(); j++) { if (j != i && (*this)[j] == child) if constexpr (nullClears) erase(j); @@ -160,71 +380,65 @@ public: (*this)[j] = NULL; } } - - // Prevent duplicate child for (U32 j = 0; j < size(); j++) AssertFatal(j == i || (*this)[j] != child, "TreeNode::setChild - Attempted to set duplicate child"); - (*this)[i] = child; child->parent = this; } - - inline TreeNode* getChild(U32 i) const - { - if (i < size()) - { - // Defensive: validate pointer before returning - if ((*this)[i] && (*this)[i]->parent != this) - { + inline TreeNode* getChild(U32 i) { + if (i < size()) { + if ((*this)[i] && (*this)[i]->parent != this) { AssertFatal(false, "TreeNode::getChild - Child parent pointer mismatch"); } return (*this)[i]; } return NULL; } - - - inline void addChildren(const Vector*>* children) + inline const TreeNode* getChild(U32 i) const { + if (i < size()) { + if ((*this)[i] && (*this)[i]->parent != this) { + AssertFatal(false, "TreeNode::getChild - Child parent pointer mismatch"); + } + return (*this)[i]; + } + return NULL; + } + inline Vector*> getChildren() const { + Vector*> children; + for (U32 i = 0; i < size(); ++i) { + if ((*this)[i]) + children.push_back((*this)[i]); + } + return children; + } + + inline void addChildren(const Vector*>* children) { if (!children) return; - for (U32 i = 0; i < children->size(); i++) - { + for (U32 i = 0; i < children->size(); i++) { TreeNode* child = (*children)[i]; if (!child) - continue; // Skip null children - - // Prevent duplicate child + continue; bool isDuplicate = false; - for (U32 j = 0; j < size(); j++) - { - if ((*this)[j] == child) - { + for (U32 j = 0; j < size(); j++) { + if ((*this)[j] == child) { isDuplicate = true; break; } } if (isDuplicate) continue; - #ifdef TORQUE_DEBUG - // Prevent cycles TreeNode* ancestor = this; - while (ancestor) - { + while (ancestor) { AssertFatal(ancestor != child, "TreeNode::addChildren - Would create circular parent relationship"); ancestor = ancestor->parent; } #endif - - // If child has a parent and it's not this, remove from old parent - if (child->parent && child->parent != this) - { - // Defensive: only remove if the parent still points to this child + if (child->parent && child->parent != this) { bool foundInParent = false; - for (U32 k = 0; k < child->parent->size(); ++k) - { - if ((*child->parent)[k] == child) - { + for (U32 k = 0; k < child->parent->size(); ++k) { + if ((*child->parent)[k] == child) { foundInParent = true; break; } @@ -234,235 +448,58 @@ public: else child->parent = NULL; } - - // Harden: skip if child is already correctly parented if (child->parent == this) continue; - child->parent = this; push_back_unique(child); } } - - inline void nullParent(bool orphan = true) - { - // Null the parent pointer with optional cleanup - // @param orphan If true (default), properly removes from parent's children array first. - // If false, only nulls parent pointer (use during manual tree destruction). - if (orphan && parent) - parent->removeChild(this); - parent = NULL; - } - -/// Sets the child pointer at index i to NULL and clears its parent pointer. -/// Does not erase the slot, regardless of nullClears. -inline void nullChild(U32 i) -{ - if (i >= size()) - return; - TreeNode* child = (*this)[i]; - if (child && child->parent == this) - child->parent = NULL; - (*this)[i] = NULL; -} - - inline bool isValidTree() const - { - // Validate tree structure - check parent-child relationships - for (U32 i = 0; i < size(); i++) - { + inline void clearChildren() { + for (U32 i = 0; i < size(); i++) { TreeNode* child = (*this)[i]; - if (child && child->parent != this) - return false; // Child doesn't point back to parent - } - return true; - } - - inline bool hasChild(TreeNode* child) const - { - if (!child) return false; - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i] == child) - return true; - } - return false; - } - - inline S32 getChildIndex(TreeNode* child) const - { - if (!child) return -1; - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i] == child) - return (S32)i; - } - return -1; - } - - inline void clearChildren() - { - // Remove all children without deleting them - for (U32 i = 0; i < size(); i++) - { - TreeNode* child = (*this)[i]; - if (child) - { - // Harden: ensure child is actually parented to this node - if (child->parent != this) - { + if (child) { + if (child->parent != this) { #ifdef TORQUE_DEBUG Con::warnf("TreeNode::clearChildren - Child at index %u has incorrect parent pointer (corruption detected)", i); #endif - // Detach anyway to avoid dangling pointer child->parent = NULL; } - else - { + else { child->parent = NULL; } } } clear(); } - - inline void swapChildren(U32 i, U32 j) - { - if (i >= size() || j >= size() || i == j) - return; - - TreeNode* childI = (*this)[i]; - TreeNode* childJ = (*this)[j]; - - // Update parent pointers if needed - if (childI && childI->parent != this) - childI->parent = this; - if (childJ && childJ->parent != this) - childJ->parent = this; - - // Swap the pointers - (*this)[i] = childJ; - (*this)[j] = childI; - } - - inline TreeNode* findChild(const T& val) - { - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i] && (*this)[i]->data == val) - return (*this)[i]; - } - return NULL; - } - - inline const TreeNode* findChild(const T& val) const - { - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i] && (*this)[i]->data == val) - return (*this)[i]; - } - return NULL; - } - - template - inline void forEachChild(Func callback) - { - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i]) - callback((*this)[i]); - } - } - - template - inline void forEachInSubtree(Func callback) - { - // Pre-order traversal: process node, then children - callback(this); - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i]) - (*this)[i]->forEachInSubtree(callback); - } - } - template - inline void forEachLeaf(Func callback) - { - if (isLeaf()) - { - callback(this); - } - else - { - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i]) - (*this)[i]->forEachLeaf(callback); - } - } - } - - inline void findLeaves(Vector*>& outLeaves) - { - forEachLeaf([&outLeaves](TreeNode* leaf) { - outLeaves.push_back_unique(leaf); - }); - } - inline U32 getTreeSize() const - { - U32 count = 1; // Count this node - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i]) - count += (*this)[i]->getTreeSize(); - } - return count; - } - - inline U32 getMaxDepth() const - { - if (isLeaf()) return 0; - - U32 maxDepth = 0; - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i]) - { - U32 childDepth = (*this)[i]->getMaxDepth(); - if (childDepth > maxDepth) - maxDepth = childDepth; - } - } - return maxDepth + 1; - } - - static void safeDetachFromParent(TreeNode* node) - { - if (!node) return; - if (node->parent) - { - // Only remove if the parent still points to this node - for (U32 i = 0; i < node->parent->size(); ++i) - { - if ((*node->parent)[i] == node) - { - node->parent->removeChild(node); - break; + inline void deleteChildren() { + Vector*> children; + for (U32 i = 0; i < size(); i++) { + TreeNode* child = (*this)[i]; + if (child) { + if (child->parent != this) { +#ifdef TORQUE_DEBUG + Con::warnf("TreeNode::deleteChildren - Child at index %u has incorrect parent pointer (corruption detected)", i); +#endif + child->parent = NULL; + } + else { + child->parent = NULL; } } + children.push_back_unique(child); + } + clear(); + for (U32 i = 0; i < children.size(); i++) { + if (children[i]) { + delete children[i]; + } } - node->parent = NULL; } - - inline void removeChild(TreeNode* child) - { + inline void removeChild(TreeNode* child) { AssertFatal(child != NULL, "TreeNode::removeChild - Attempted to remove NULL child"); bool found = false; - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i] == child) - { + for (U32 i = 0; i < size(); i++) { + if ((*this)[i] == child) { child->parent = NULL; if constexpr (nullClears) erase(i); @@ -472,15 +509,10 @@ inline void nullChild(U32 i) break; } } - if (!found) - { + if (!found) { #ifdef TORQUE_DEBUG - // Defensive: attempt to repair if possible - for (U32 i = 0; i < size(); i++) - { - if ((*this)[i] && (*this)[i]->parent == this) - { - // Child pointer mismatch, but parent pointer matches: repair + for (U32 i = 0; i < size(); i++) { + if ((*this)[i] && (*this)[i]->parent == this) { (*this)[i]->parent = NULL; if constexpr (nullClears) erase(i); @@ -497,9 +529,7 @@ inline void nullChild(U32 i) } AssertFatal(found, "TreeNode::removeChild - Child not found in parent's children array"); } - - inline void removeChild(U32 i) - { + inline void removeChild(U32 i) { AssertFatal(i < size(), "TreeNode::removeChild - Index out of bounds"); TreeNode* child = (*this)[i]; if (child) @@ -509,211 +539,201 @@ inline void nullChild(U32 i) else (*this)[i] = NULL; } - - inline void replaceChild(TreeNode* oldChild, TreeNode* newChild) - { + inline void replaceChild(TreeNode* oldChild, TreeNode* newChild) { AssertFatal(oldChild != NULL, "TreeNode::replaceChild - Attempted to replace NULL child"); - S32 idx = getChildIndex(oldChild); AssertFatal(idx != -1, "TreeNode::replaceChild - oldChild not found in children"); - - // Use hardened setChild for all safety checks and pointer management setChild((U32)idx, newChild); } - - inline void compact() - { - for (U32 i = 0; i < size(); ) - { - TreeNode* child = (*this)[i]; - if constexpr (nullClears) - { - // Compact style: erase NULL slots, keep only valid children. - if (child == NULL) - { - erase(i); - continue; - } - // Repair parent pointer if needed. - if (child->parent != this) - child->parent = this; - i++; - } - else - { - // BVH/slot-stable style: leave NULLs, just repair parent pointers. - if (child && child->parent != this) - child->parent = this; - i++; - } - } + inline void nullChild(U32 i) { + if (i >= size()) + return; + TreeNode* child = (*this)[i]; + if (child && child->parent == this) + child->parent = NULL; + (*this)[i] = NULL; + } + inline void swapChildren(U32 i, U32 j) { + if (i >= size() || j >= size() || i == j) + return; + TreeNode* childI = (*this)[i]; + TreeNode* childJ = (*this)[j]; + if (childI && childI->parent != this) + childI->parent = this; + if (childJ && childJ->parent != this) + childJ->parent = this; + (*this)[i] = childJ; + (*this)[j] = childI; } - inline bool hasChildren() const - { - if constexpr (nullClears) - return size() > 0; - else { - for (U32 i = 0; i < size(); ++i) - if ((*this)[i]) return true; - return false; - } - } - - inline U32 getNumChildren() const { - if constexpr (nullClears) - return size(); - else { - U32 count = 0; - for (U32 i = 0; i < size(); ++i) - if ((*this)[i]) ++count; - return count; - } - } - - inline Vector*> getChildren() const - { - Vector*> children; - if constexpr (nullClears) { - // All slots are valid children - children = *this; - } - else { - // Only non-NULL slots are valid children - children.reserve(size()); - for (U32 i = 0; i < size(); ++i) - if ((*this)[i]) - children.push_back_unique((*this)[i]); - } - return children; - } - - inline void deleteChildren() - { - Vector*> children; - for (U32 i = 0; i < size(); i++) - { - TreeNode* child = (*this)[i]; - if (child) - { - if (child->parent != this) - { -#ifdef TORQUE_DEBUG - Con::warnf("TreeNode::deleteChildren - Child at index %u has incorrect parent pointer (corruption detected)", i); -#endif - // Detach anyway to avoid dangling pointer - child->parent = NULL; - } - else - { - child->parent = NULL; - } - } - children.push_back_unique(child); - } - clear(); - - for (U32 i = 0; i < children.size(); i++) - { - if (children[i]) - { - // Child should already be detached - delete children[i]; - } - } - } - - inline void collectSubtree(Vector*>& nodes) - { - // Pre-order collection of all nodes in subtree - forEachInSubtree([&nodes](TreeNode* n) { - nodes.push_back_unique(n); - }); - } - - inline void collectSubtreeData(Vector& dataList) - { - // Collect just the data from subtree - forEachInSubtree([&dataList](TreeNode* n) { - dataList.push_back_unique(n->data); - }); - } - - void collectAncestors(Vector*>& outAncestors) const - { - TreeNode* node = parent; - while (node) - { - outAncestors.push_back_unique(node); - node = node->parent; - } - } - - //sibling logic - inline Vector*> getSiblings() const - { + //------------------------------------------------------------------------- + // Sibling Logic + //------------------------------------------------------------------------- + inline Vector*> getSiblings() const { Vector*> siblings; if (!parent) return siblings; - siblings.reserve(parent->size() - 1); - for (U32 i = 0; i < parent->size(); i++) - { + for (U32 i = 0; i < parent->size(); i++) { TreeNode* sibling = (*parent)[i]; - if (sibling && sibling != this) // Skip NULLs + if (sibling && sibling != this) siblings.push_back_unique(sibling); } return siblings; } - //data logic - inline T getData() const { return data; } - inline void setData(const T& val) { data = val; } - inline void operator =(const T& val) { data = val; } - inline T* getDataPtr() { return &data; } - - // Traversal helpers - inline U32 getDepth() const - { - U32 depth = 0; - TreeNode* current = parent; - while (current) - { - depth++; - current = current->parent; + //------------------------------------------------------------------------- + // Traversal & Collection + //------------------------------------------------------------------------- + inline void findLeaves(Vector*>& out) { + if (isLeaf()) + out.push_back_unique(this); + else + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) + (*this)[i]->getLeaves(out); + } + inline void collectSubtree(Vector*>& nodes) { + nodes.push_back_unique(this); + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) + (*this)[i]->collectSubtree(nodes); + } + inline void collectSubtreeData(Vector& dataList) { + dataList.push_back_unique(data); + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) + (*this)[i]->collectSubtreeData(dataList); +} + // callback signature: void(TreeNode*) + template + inline void forEachChild(Func callback) { + for (U32 i = 0; i < size(); i++) + if ((*this)[i]) + callback((*this)[i]); + } + // callback signature: void(const TreeNode*) + template + inline void forEachChild(Func callback) const { + for (U32 i = 0; i < size(); i++) + if ((*this)[i]) + callback((*this)[i]); + } + // callback signature: void(TreeNode*) + template + inline void forEachInSubtree(Func callback) { + callback(this); + for (U32 i = 0; i < size(); i++) + if ((*this)[i]) + (*this)[i]->forEachInSubtree(callback); + } + // callback signature: void(const TreeNode*) + template + inline void forEachLeaf(Func callback) { + if (isLeaf()) + callback(this); + else + for (U32 i = 0; i < size(); i++) + if ((*this)[i]) + (*this)[i]->forEachLeaf(callback); + } + // callback signature: void(TreeNode*) + template + void traverseBreadthFirst(Func callback) { + Vector*> queue; + queue.push_back(this); + while (!queue.empty()) { + TreeNode* node = queue.first(); + queue.erase(0); + callback(node); + for (U32 i = 0; i < node->size(); ++i) + if ((*node)[i]) + queue.push_back((*node)[i]); } - return depth; } - - inline TreeNode* getRoot() + // Calls the provided functor on each ancestor up to the root (excluding this node). + // Typically used for propagating updates up the tree (e.g., refitting bounds). + // callback signature: void(TreeNode*) + template + void refitPathToRoot(Func refitFunc) { - TreeNode* current = this; - while (current->parent) - current = current->parent; - return current; - } - - inline const TreeNode* getRoot() const - { - const TreeNode* current = this; - while (current->parent) - current = current->parent; - return current; - } - - /// Returns true if this node is present in the given vector (used for cycle detection). - bool isVisited(const Vector*>& visited) const - { - for (U32 i = 0; i < visited.size(); ++i) + TreeNode* node = parent; + while (node) { - if (visited[i] == this) - return true; + refitFunc(node); + node = node->parent; } - return false; + } + // callback signature: TreeNode* pred(TreeNode*) + template + TreeNode* findIf(Func pred) { + if (pred(this)) return this; + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) { + auto* found = (*this)[i]->findIf(pred); + if (found) return found; + } + return NULL; + } + // callback signature: const TreeNode* pred(const TreeNode*) + template + void findAllIf(Func pred, Vector*>& out) { + if (pred(this)) + out.push_back(this); + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) + (*this)[i]->findAllIf(pred, out); + } + // callback signature: const TreeNode* pred(const TreeNode*) + template + U32 countIf(Func pred) const { + U32 count = pred(this) ? 1 : 0; + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) + count += (*this)[i]->countIf(pred); + return count; + } + // callback signature: bool pred(TreeNode*) + template + void removeIf(Func pred) { + for (S32 i = size() - 1; i >= 0; --i) { + if ((*this)[i] && pred((*this)[i])) { + delete (*this)[i]; + if constexpr (nullClears) + erase(i); + else + (*this)[i] = NULL; + } + else if ((*this)[i]) { + (*this)[i]->removeIf(pred); + } + } + } + //------------------------------------------------------------------------- + // Tree Metrics + //------------------------------------------------------------------------- + inline U32 getTreeSize() const { + U32 count = 1; + for (U32 i = 0; i < size(); i++) + if ((*this)[i]) + count += (*this)[i]->getTreeSize(); + return count; + } + inline U32 getMaxDepth() const { + if (isLeaf()) return 0; + U32 maxDepth = 0; + for (U32 i = 0; i < size(); i++) + if ((*this)[i]) { + U32 childDepth = (*this)[i]->getMaxDepth(); + if (childDepth > maxDepth) + maxDepth = childDepth; + } + return maxDepth + 1; } - // Gather up to maxCandidates leaves or grandchildren for rotation from a node. - static U32 gatherRotationCandidates(TreeNode* parent, Vector*>& outNodes, U32 maxCandidates = 8) - { + //------------------------------------------------------------------------- + // Rotation/Balance Utilities + //------------------------------------------------------------------------- + static U32 gatherRotationCandidates(TreeNode* parent, Vector*>& outNodes, U32 maxCandidates = 8) { outNodes.clear(); for (U32 i = 0; i < parent->size() && outNodes.size() < maxCandidates; ++i) { TreeNode* child = (*parent)[i]; @@ -730,9 +750,6 @@ inline void nullChild(U32 i) } return outNodes.size(); } - - // Finds the best way to pair/group N nodes into two groups of groupSize each, minimizing cost. - // CostFunc: F32 (*costFunc)(const TreeNode*, const TreeNode*) static bool findBestRotationPairing( const Vector*>& nodes, U32 groupSize, @@ -743,18 +760,14 @@ inline void nullChild(U32 i) { const U32 n = nodes.size(); if (n < 2 * groupSize) return false; - outBestCost = F32_MAX; bool found = false; - Vector indices; indices.setSize(n); for (U32 i = 0; i < n; ++i) indices[i] = i; - Vector groupA, groupB; groupA.setSize(groupSize); groupB.setSize(groupSize); - while (true) { for (U32 i = 0; i < groupSize; ++i) groupA[i] = indices[i]; @@ -772,7 +785,6 @@ inline void nullChild(U32 i) found = true; } } - S32 k = n - 2; while (k >= 0 && indices[k] >= indices[k + 1]) --k; if (k < 0) break; @@ -786,26 +798,40 @@ inline void nullChild(U32 i) return found; } - //converters + //------------------------------------------------------------------------- + // Data Accessors + //------------------------------------------------------------------------- + inline T getData() const { return data; } + inline void setData(const T& val) { data = val; } + inline void operator =(const T& val) { data = val; } + inline T* getDataPtr() { return &data; } + inline const T* getDataPtr() const { return &data; } + //------------------------------------------------------------------------- + // Type Conversion Helpers + //------------------------------------------------------------------------- template Derived* getChildAs(U32 i) { return static_cast(getChild(i)); } template const Derived* getChildAs(U32 i) const { return static_cast(getChild(i)); } - template - Vector getChildrenAs() const - { + Vector getChildrenAs() { Vector out; for (U32 i = 0; i < size(); ++i) if ((*this)[i]) out.push_back(static_cast((*this)[i])); return out; } - + template + Vector getChildrenAs() const { + Vector out; + for (U32 i = 0; i < size(); ++i) + if ((*this)[i]) + out.push_back(static_cast((*this)[i])); + return out; + } template - inline void forEachLeafAs(Func callback) - { + inline void forEachLeafAs(Func callback) { if (isLeaf()) callback(static_cast(this)); else @@ -813,7 +839,15 @@ inline void nullChild(U32 i) if ((*this)[i]) (*this)[i]->template forEachLeafAs(callback); } - + template + inline void forEachLeafAs(Func callback) const { + if (isLeaf()) + callback(static_cast(this)); + else + for (U32 i = 0; i < size(); i++) + if ((*this)[i]) + (*this)[i]->template forEachLeafAs(callback); + } }; class TreeObject : public SimObject { From c4e973c6e914c63d8cff9e66145857795869490e Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Tue, 3 Feb 2026 21:21:24 -0600 Subject: [PATCH 10/17] while modern compilers *should* already treat template methods as inherently inline, never hurst to spell it out --- Engine/source/core/util/treeObject.h | 73 ++++++++++++++-------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index ab721ed60..e2e77c220 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -40,10 +40,10 @@ public: //------------------------------------------------------------------------- // Constructors / Destructor //------------------------------------------------------------------------- - TreeNode(const T& val = T(), TreeNode* p = NULL) + inline TreeNode(const T& val = T(), TreeNode* p = NULL) : data(val), parent(p) {} - TreeNode(const Vector*>& children, TreeNode* p = NULL) + inline TreeNode(const Vector*>& children, TreeNode* p = NULL) : data(T()), parent(p) { increment(children.size()); @@ -65,7 +65,7 @@ public: // Deep Copy / Cloning //------------------------------------------------------------------------- // Creates a deep copy of this node and its entire subtree. - TreeNode* clone() const { + inline TreeNode* clone() const { auto* copy = new TreeNode(data, NULL); for (U32 i = 0; i < size(); ++i) if ((*this)[i]) @@ -74,7 +74,7 @@ public: } template - TreeNode* cloneIf(Func pred) const { + inline TreeNode* cloneIf(Func pred) const { if (!pred(this)) return NULL; auto* newNode = new TreeNode(data, NULL); for (U32 i = 0; i < size(); ++i) @@ -86,7 +86,7 @@ public: return newNode; } - bool equals(const TreeNode* other) const { + inline bool equals(const TreeNode* other) const { if (!other || data != other->data || size() != other->size()) return false; for (U32 i = 0; i < size(); ++i) { @@ -100,23 +100,23 @@ public: } return true; } - // Stitches 'subtree' as a child of 'atNode' (re-parents, does not clone). - static TreeNode* mergeAt(TreeNode* atNode, TreeNode* subtree) { + + static inline TreeNode* mergeAt(TreeNode* atNode, TreeNode* subtree) { if (!atNode || !subtree) return NULL; atNode->addChild(subtree); return subtree; } - // Clones 'subtree' and attaches the clone as a child of 'atNode'. - static TreeNode* cloneAt(TreeNode* atNode, const TreeNode* subtree) { + static inline TreeNode* cloneAt(TreeNode* atNode, const TreeNode* subtree) { if (!atNode || !subtree) return NULL; TreeNode* newClone = subtree->clone(); atNode->addChild(newClone); return newClone; } - bool isSubtreeOf(const TreeNode* other) const { + + inline bool isSubtreeOf(const TreeNode* other) const { if (!other) return false; if (equals(other)) return true; for (U32 i = 0; i < other->size(); ++i) @@ -126,7 +126,8 @@ public: } // Assignment operator: shallow copy of data and parent pointer only. // Does NOT copy children or subtree. - TreeNode& operator=(const TreeNode& other) { + + inline TreeNode& operator=(const TreeNode& other) { if (this == &other) return *this; data = other.data; @@ -190,14 +191,14 @@ public: } return true; } - bool isVisited(const Vector*>& visited) const { + inline bool isVisited(const Vector*>& visited) const { for (U32 i = 0; i < visited.size(); ++i) if (visited[i] == this) return true; return false; } template - TreeNode* findLeafIf(Func pred) { + inline TreeNode* findLeafIf(Func pred) { if (isLeaf() && pred(this)) return this; for (U32 i = 0; i < size(); ++i) if ((*this)[i]) { @@ -223,7 +224,7 @@ public: current = current->parent; return current; } - bool isAncestorOf(const TreeNode* other) const { + inline bool isAncestorOf(const TreeNode* other) const { const TreeNode* node = other->parent; while (node) { if (node == this) return true; @@ -231,10 +232,10 @@ public: } return false; } - bool isDescendantOf(const TreeNode* other) const { + inline bool isDescendantOf(const TreeNode* other) const { return other->isAncestorOf(this); } - TreeNode* lowestCommonAncestor(TreeNode* other) { + inline TreeNode* lowestCommonAncestor(TreeNode* other) { Vector*> pathA, pathB; getPathToRoot(pathA); other->getPathToRoot(pathB); @@ -246,7 +247,7 @@ public: } return lca; } - void getPathToRoot(Vector*>& out) const { + inline void getPathToRoot(Vector*>& out) const { const TreeNode* node = this; while (node) { out.push_back(node); @@ -264,7 +265,7 @@ public: parent->removeChild(this); parent = NULL; } - static void safeDetachFromParent(TreeNode* node) { + static inline void safeDetachFromParent(TreeNode* node) { if (!node) return; if (node->parent) { for (U32 i = 0; i < node->parent->size(); ++i) { @@ -276,14 +277,14 @@ public: } node->parent = NULL; } - void collectAncestors(Vector*>& outAncestors) const { + inline void collectAncestors(Vector*>& outAncestors) const { TreeNode* node = parent; while (node) { outAncestors.push_back_unique(node); node = node->parent; } } - void collectAncestorsSortedByDepth(const Vector*>& leaves, Vector*>& outAncestors) const { + inline void collectAncestorsSortedByDepth(const Vector*>& leaves, Vector*>& outAncestors) const { Map*, bool> ancestorSet; for (U32 i = 0; i < leaves.size(); ++i) { TreeNode* node = leaves[i]->getParent(); @@ -312,7 +313,7 @@ public: return depth; } template - void forEachAncestor(Func callback) const { + inline void forEachAncestor(Func callback) const { const TreeNode* node = parent; while (node) { callback(node); @@ -603,7 +604,7 @@ public: for (U32 i = 0; i < size(); ++i) if ((*this)[i]) (*this)[i]->collectSubtreeData(dataList); -} + } // callback signature: void(TreeNode*) template inline void forEachChild(Func callback) { @@ -638,7 +639,7 @@ public: } // callback signature: void(TreeNode*) template - void traverseBreadthFirst(Func callback) { + inline void traverseBreadthFirst(Func callback) { Vector*> queue; queue.push_back(this); while (!queue.empty()) { @@ -654,18 +655,16 @@ public: // Typically used for propagating updates up the tree (e.g., refitting bounds). // callback signature: void(TreeNode*) template - void refitPathToRoot(Func refitFunc) - { + inline void refitPathToRoot(Func refitFunc) { TreeNode* node = parent; - while (node) - { + while (node) { refitFunc(node); node = node->parent; } } // callback signature: TreeNode* pred(TreeNode*) template - TreeNode* findIf(Func pred) { + inline TreeNode* findIf(Func pred) { if (pred(this)) return this; for (U32 i = 0; i < size(); ++i) if ((*this)[i]) { @@ -676,7 +675,7 @@ public: } // callback signature: const TreeNode* pred(const TreeNode*) template - void findAllIf(Func pred, Vector*>& out) { + inline void findAllIf(Func pred, Vector*>& out) { if (pred(this)) out.push_back(this); for (U32 i = 0; i < size(); ++i) @@ -685,7 +684,7 @@ public: } // callback signature: const TreeNode* pred(const TreeNode*) template - U32 countIf(Func pred) const { + inline U32 countIf(Func pred) const { U32 count = pred(this) ? 1 : 0; for (U32 i = 0; i < size(); ++i) if ((*this)[i]) @@ -694,7 +693,7 @@ public: } // callback signature: bool pred(TreeNode*) template - void removeIf(Func pred) { + inline void removeIf(Func pred) { for (S32 i = size() - 1; i >= 0; --i) { if ((*this)[i] && pred((*this)[i])) { delete (*this)[i]; @@ -733,7 +732,7 @@ public: //------------------------------------------------------------------------- // Rotation/Balance Utilities //------------------------------------------------------------------------- - static U32 gatherRotationCandidates(TreeNode* parent, Vector*>& outNodes, U32 maxCandidates = 8) { + static inline U32 gatherRotationCandidates(TreeNode* parent, Vector*>& outNodes, U32 maxCandidates = 8) { outNodes.clear(); for (U32 i = 0; i < parent->size() && outNodes.size() < maxCandidates; ++i) { TreeNode* child = (*parent)[i]; @@ -750,7 +749,7 @@ public: } return outNodes.size(); } - static bool findBestRotationPairing( + static inline bool findBestRotationPairing( const Vector*>& nodes, U32 groupSize, F32(*costFunc)(const TreeNode*, const TreeNode*), @@ -811,11 +810,11 @@ public: // Type Conversion Helpers //------------------------------------------------------------------------- template - Derived* getChildAs(U32 i) { return static_cast(getChild(i)); } + inline Derived* getChildAs(U32 i) { return static_cast(getChild(i)); } template - const Derived* getChildAs(U32 i) const { return static_cast(getChild(i)); } + inline const Derived* getChildAs(U32 i) const { return static_cast(getChild(i)); } template - Vector getChildrenAs() { + inline Vector getChildrenAs() { Vector out; for (U32 i = 0; i < size(); ++i) if ((*this)[i]) @@ -823,7 +822,7 @@ public: return out; } template - Vector getChildrenAs() const { + inline Vector getChildrenAs() const { Vector out; for (U32 i = 0; i < size(); ++i) if ((*this)[i]) From cd8c944989c484efedbbf0049bab43dfacdc1e1b Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sat, 7 Mar 2026 09:36:46 -0600 Subject: [PATCH 11/17] vector work to allow it to take an allocator, as well as a fixed count and skip the tracking vars. --- Engine/source/console/engineStructs.h | 2 - Engine/source/console/propertyParsing.h | 1 - Engine/source/core/util/str.h | 7 +- Engine/source/core/util/tVector.cpp | 254 ++- Engine/source/core/util/tVector.h | 1900 +++++++++++++---- .../source/core/util/tVectorSpecializations.h | 2 + Engine/source/lighting/lightingInterfaces.h | 3 +- Engine/source/platform/platform.h | 12 +- Engine/source/platform/platformFont.h | 2 +- Engine/source/scene/sceneContainer.cpp | 2 +- Engine/source/scene/sceneObject.cpp | 26 +- Engine/source/scene/sceneObject.h | 1 + 12 files changed, 1768 insertions(+), 444 deletions(-) diff --git a/Engine/source/console/engineStructs.h b/Engine/source/console/engineStructs.h index 57b9a4050..d6c7f1f7e 100644 --- a/Engine/source/console/engineStructs.h +++ b/Engine/source/console/engineStructs.h @@ -33,8 +33,6 @@ /// @file /// Definitions for the core engine structured types. - -template< typename T > class Vector; namespace Torque { class UUID; } diff --git a/Engine/source/console/propertyParsing.h b/Engine/source/console/propertyParsing.h index 69bdc09f6..91eeb07b3 100644 --- a/Engine/source/console/propertyParsing.h +++ b/Engine/source/console/propertyParsing.h @@ -47,7 +47,6 @@ class String; class FileName; class SimObject; class SimObjectType; -template class Vector; //----------------------------------------------------------------------------- // String scan/print methods diff --git a/Engine/source/core/util/str.h b/Engine/source/core/util/str.h index 827ba0c1f..7cb48e9f5 100644 --- a/Engine/source/core/util/str.h +++ b/Engine/source/core/util/str.h @@ -30,9 +30,8 @@ #endif #include - -template< class T > class Vector; - +template struct CustomAllocator; +template class Vector; typedef UTF8 StringChar; @@ -147,7 +146,7 @@ public: String collapseEscapes() const; /// Split the string into its components separated by the given delimiter. - void split( const char* delimiter, Vector< String >& outElements ) const; + void split( const char* delimiter, Vector >& outElements ) const; /// Return true if the string starts with the given text. bool startsWith( const char* text ) const; diff --git a/Engine/source/core/util/tVector.cpp b/Engine/source/core/util/tVector.cpp index 42ef4b21a..0993fb08f 100644 --- a/Engine/source/core/util/tVector.cpp +++ b/Engine/source/core/util/tVector.cpp @@ -24,54 +24,16 @@ #include "core/util/tVector.h" #include "platform/profiler.h" +#include "console/engineAPI.h" #ifdef TORQUE_DEBUG_GUARD -bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize, - const char* fileName, - const U32 lineNum) +bool VectorResize(U32* aSize, U32* aCount, void** arrayPtr, U32 newCount, U32 elemSize, + const char* fileName, + const U32 lineNum) { - PROFILE_SCOPE( VectorResize ); - - if (newCount > 0) - { - U32 blocks = newCount / VectorBlockSize; - if (newCount % VectorBlockSize) - blocks++; - S32 mem_size = blocks * VectorBlockSize * elemSize; - - const char* pUseFileName = fileName != NULL ? fileName : __FILE__; - U32 useLineNum = fileName != NULL ? lineNum : __LINE__; - - if (*arrayPtr != NULL) - *arrayPtr = dRealloc_r(*arrayPtr, mem_size, pUseFileName, useLineNum); - else - *arrayPtr = dMalloc_r(mem_size, pUseFileName, useLineNum); - - AssertFatal( *arrayPtr, "VectorResize - Allocation failed." ); - - *aCount = newCount; - *aSize = blocks * VectorBlockSize; - return true; - } - - if (*arrayPtr) - { - dFree(*arrayPtr); - *arrayPtr = 0; - } - - *aSize = 0; - *aCount = 0; - return true; -} - -#else - -bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize) -{ - PROFILE_SCOPE( VectorResize ); + PROFILE_SCOPE(VectorResize); if (newCount > 0) { @@ -79,7 +41,46 @@ bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 el if (newCount % VectorBlockSize) blocks++; S32 mem_size = blocks * VectorBlockSize * elemSize; - *arrayPtr = *arrayPtr ? dRealloc(*arrayPtr,mem_size) : + + const char* pUseFileName = fileName != NULL ? fileName : __FILE__; + U32 useLineNum = fileName != NULL ? lineNum : __LINE__; + + if (*arrayPtr != NULL) + *arrayPtr = dRealloc_r(*arrayPtr, mem_size, pUseFileName, useLineNum); + else + *arrayPtr = dMalloc_r(mem_size, pUseFileName, useLineNum); + + AssertFatal(*arrayPtr, "VectorResize - Allocation failed."); + + *aCount = newCount; + *aSize = blocks * VectorBlockSize; + return true; + } + + if (*arrayPtr) + { + dFree(*arrayPtr); + *arrayPtr = 0; + } + + *aSize = 0; + *aCount = 0; + return true; +} + +#else + +bool VectorResize(U32* aSize, U32* aCount, void** arrayPtr, U32 newCount, U32 elemSize) +{ + PROFILE_SCOPE(VectorResize); + + if (newCount > 0) + { + U32 blocks = newCount / VectorBlockSize; + if (newCount % VectorBlockSize) + blocks++; + S32 mem_size = blocks * VectorBlockSize * elemSize; + *arrayPtr = *arrayPtr ? dRealloc(*arrayPtr, mem_size) : dMalloc(mem_size); *aCount = newCount; @@ -87,7 +88,7 @@ bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 el return true; } - if (*arrayPtr) + if (*arrayPtr) { dFree(*arrayPtr); *arrayPtr = 0; @@ -99,3 +100,166 @@ bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 el } #endif + +void test_Vector_dynamic() { + Vector v; + AssertFatal(v.size() == 0, "Initial size should be 0"); + AssertFatal(v.empty(), "Vector should be empty"); + + // push_back, push_front, front, back + v.push_back(42); + AssertFatal(v.size() == 1, "Size after push_back should be 1"); + AssertFatal(v[0] == 42, "push_back value incorrect"); + v.push_front(7); + AssertFatal(v.size() == 2, "Size after push_front should be 2"); + AssertFatal(v[0] == 7 && v[1] == 42, "push_front value incorrect"); + AssertFatal(v.front() == 7, "front() incorrect"); + AssertFatal(v.back() == 42, "back() incorrect"); + + // insert, erase, erase_fast + v.insert(1, 99); + AssertFatal(v.size() == 3, "Size after insert should be 3"); + AssertFatal(v[1] == 99, "insert value incorrect"); + v.erase(1); + AssertFatal(v.size() == 2, "Size after erase should be 2"); + v.push_back(55); + v.erase_fast(static_cast < U32>(0)); + AssertFatal(v.size() == 2, "Size after erase_fast should be 2"); + + // find_next, contains, remove + AssertFatal(v.find_next(42) != -1, "find_next failed"); + AssertFatal(v.contains(42), "contains failed"); + AssertFatal(v.remove(42), "remove failed"); + AssertFatal(!v.contains(42), "remove did not remove value"); + + // pop_front, pop_back + v.push_back(88); + v.pop_front(); + AssertFatal(v.size() == 1, "pop_front failed"); + v.pop_back(); + AssertFatal(v.size() == 0, "pop_back failed"); + + // reserve, capacity, setSize, set, clear, compact, fill, copy, merge, reverse + v.reserve(5); + AssertFatal(v.capacity() >= 5, "reserve/capacity failed"); + v.setSize(3); + AssertFatal(v.size() == 3, "setSize failed"); + U8 arr[3] = { 1,2,3 }; + v.set(arr, 3); + for (U32 i = 0; i < 3; ++i) AssertFatal(v[i] == arr[i], "set failed"); + v.clear(); + AssertFatal(v.size() == 0, "clear failed"); + v.setSize(3); + v.fill(9); + for (U32 i = 0; i < 3; ++i) AssertFatal(v[i] == 9, "fill failed"); + U8 arr2[3] = { 4,5,6 }; + v.copy(arr2); + for (U32 i = 0; i < 3; ++i) AssertFatal(v[i] == arr2[i], "copy failed"); + v.reverse(); + AssertFatal(v[0] == 6 && v[2] == 4, "reverse failed"); + Vector v2; + v2.set(arr, 3); + v.merge(v2); + AssertFatal(v.size() == 6, "merge failed"); + + // increment, decrement + v.increment(); + AssertFatal(v.size() == 7, "increment failed"); + v.decrement(); + AssertFatal(v.size() == 6, "decrement failed"); + v.increment(2); + AssertFatal(v.size() == 8, "increment(n) failed"); + v.decrement(2); + AssertFatal(v.size() == 6, "decrement(n) failed"); + + // insert (block), fill(count), fill(count, offset) + U8 block[2] = { 10,11 }; + v.insert(block, 2, 2); + AssertFatal(v[2] == 10 && v[3] == 11, "block insert failed"); + v.fill(7, 2); + for (U32 i = 0; i < 2; ++i) AssertFatal(v[i] == 7, "fill(count) failed"); + v.fill(8, 2, 2); + AssertFatal(v[2] == 8 && v[3] == 8, "fill(count,offset) failed"); + + // assign, moveFrom, swap, compare, forEach, removeIf, countIf, anyOf, allOf + Vector v3; + v3.set(arr, 3); + v.assign(&v3, 3, 0, 0); + for (U32 i = 0; i < 3; ++i) AssertFatal(v[i] == arr[i], "assign failed"); + v3.moveFrom(v); + AssertFatal(v3.size() == 3 && v.size() == 0, "moveFrom failed"); + v.set(arr, 3); + v.swap(v3); + for (U32 i = 0; i < 3; ++i) AssertFatal(v[i] == arr[i], "swap failed"); + AssertFatal(v.compare(v3), "compare failed"); + v.forEach([](U8& x) { x = 1; }); + for (U32 i = 0; i < v.size(); ++i) AssertFatal(v[i] == 1, "forEach failed"); + v.removeIf([](U8 x) { return x == 1; }); + AssertFatal(v.size() == 0, "removeIf failed"); + v.set(arr, 3); + AssertFatal(v.countIf([](U8 x) { return x > 1; }) == 2, "countIf failed"); + AssertFatal(v.anyOf([](U8 x) { return x == 2; }), "anyOf failed"); + AssertFatal(v.allOf([](U8 x) { return x > 0; }), "allOf failed"); +} + +void test_Vector_fixed() { + Vector v; + AssertFatal(v.size() == 10, "Fixed vector size should be 10"); + + // fill, operator[], front, back, first, last + v.fill(5); + for (U32 i = 0; i < v.size(); ++i) AssertFatal(v[i] == 5, "fill failed"); + v[3] = 99; + AssertFatal(v[3] == 99, "operator[] failed"); + AssertFatal(v.front() == 5, "front failed"); + AssertFatal(v.back() == 5, "back failed"); + AssertFatal(v.first() == 5, "first failed"); + AssertFatal(v.last() == 5, "last failed"); + + // reverse, swap, erase, erase_fast, contains, remove + v.reverse(); + AssertFatal(v[6] == 99, "reverse failed"); + Vector v2; + v2.fill(1); + v.swap(v2); + for (U32 i = 0; i < v.size(); ++i) AssertFatal(v[i] == 1 && v2[i] == 5, "swap failed"); + v.erase(static_cast (0)); + AssertFatal(v.size() == 10, "erase should not change size for fixed vector"); + v.erase_fast(1); + AssertFatal(v.size() == 10, "erase_fast should not change size for fixed vector"); + AssertFatal(v.contains(1), "contains failed"); + v.remove(1); + AssertFatal(v.contains(1), "remove should not change size for fixed vector"); + + // fill(count), fill(count, offset), copy, merge, assign, compare + v.fill(2, 5); + for (U32 i = 0; i < 5; ++i) AssertFatal(v[i] == 2, "fill(count) failed"); + v.fill(3, 2, 5); + AssertFatal(v[5] == 3 && v[6] == 3, "fill(count,offset) failed"); + U8 arr[10] = { 0,1,2,3,4,5,6,7,8,9 }; + v.copy(arr); + for (U32 i = 0; i < 10; ++i) AssertFatal(v[i] == arr[i], "copy failed"); + v2.copy(arr); + v.merge(v2); + AssertFatal(v.size() == 10, "merge should not change size for fixed vector"); + v.assign(&v2, 10, 0, 0); + for (U32 i = 0; i < 10; ++i) AssertFatal(v[i] == arr[i], "assign failed"); + AssertFatal(v.compare(v2), "compare failed"); + + // forEach, removeIf, countIf, anyOf, allOf + v.forEach([](U8& x) { x = 4; }); + for (U32 i = 0; i < v.size(); ++i) AssertFatal(v[i] == 4, "forEach failed"); + v.removeIf([](U8 x) { return x == 4; }); + AssertFatal(v.size() == 10, "removeIf should not change size for fixed vector"); + v.copy(arr); + AssertFatal(v.countIf([](U8 x) { return x > 5; }) == 4, "countIf failed"); + AssertFatal(v.anyOf([](U8 x) { return x == 7; }), "anyOf failed"); + AssertFatal(v.allOf([](U8 x) { return x < 10; }), "allOf failed"); +} + +DefineEngineFunction(test_Vector, void, (), , + "Test the Vector class with both dynamic and fixed-size vectors.") +{ + test_Vector_dynamic(); + test_Vector_fixed(); +} diff --git a/Engine/source/core/util/tVector.h b/Engine/source/core/util/tVector.h index c7c4fc7a4..79ade2fe4 100644 --- a/Engine/source/core/util/tVector.h +++ b/Engine/source/core/util/tVector.h @@ -28,6 +28,8 @@ #ifndef _PLATFORM_H_ #include "platform/platform.h" #endif +#include +#include #include #include "console/engineTypes.h" #include "console/engineTypeInfo.h" @@ -37,12 +39,13 @@ /// Size of memory blocks to allocate at a time for vectors. const static S32 VectorBlockSize = 16; + #ifdef TORQUE_DEBUG_GUARD -extern bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize, - const char* fileName, - const U32 lineNum); +extern bool VectorResize(U32* aSize, U32* aCount, void** arrayPtr, U32 newCount, U32 elemSize, + const char* fileName, + const U32 lineNum); #else -extern bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize); +extern bool VectorResize(U32* aSize, U32* aCount, void** arrayPtr, U32 newCount, U32 elemSize); #endif /// Use the following macro to bind a vector to a particular line @@ -53,6 +56,731 @@ extern bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, #define VECTOR_SET_ASSOCIATION(x) #endif +//----------------------------------------------------------------------------- +// Custom allocator using dMalloc/dFree + +// typetrait defs +template +struct VectorTrackingTokens { + U32 arraySize() const { return COUNT; } + U32 elementCount() const { return COUNT; } + void setArraySize(U32) {} // no-op + void setElementCount(U32) {} // no-op + /* + bool mOwnsMemory = false; + bool ownsMemory() const { return mOwnsMemory; } + void setOwnsMemory(bool owns) { mOwnsMemory = owns; } + */ +}; + +template<> +struct VectorTrackingTokens<0> { + U32 mArraySize = 0; + U32 mElementCount = 0; + U32 arraySize() const { return mArraySize; } + U32 elementCount() const { return mElementCount; } + void setArraySize(U32 sz) { mArraySize = sz; } + void setElementCount(U32 sz) { mElementCount = sz; } + /* + bool mOwnsMemory = false; + bool ownsMemory() const { return mOwnsMemory; } + void setOwnsMemory(bool owns) { mOwnsMemory = owns; } + */ +}; + +template +struct VectorTraits; + +template +struct VectorTraits> +{ + static constexpr U32 COUNT = COUNT; + using allocator = Allocator; + using value_type = T; +}; + +//----------------------------------------------------------------------------- +// Custom allocator using dMalloc/dFree +template +struct CustomAllocator +{ + using State = VectorTrackingTokens; + static T* allocate(U32 count = COUNT) + { + if (COUNT > 0) + return reinterpret_cast(dMalloc(COUNT * sizeof(T))); + else + return reinterpret_cast(dMalloc(count * sizeof(T))); + } + static void deallocate(T* ptr) + { + dFree(ptr); + } +}; + +//----------------------------------------------------------------------------- +// VectorHelpers namespace +// +// Purpose: +// - Provides extensible, utility functions for vector operations. +// - Supports bulk, cross-type, and advanced operations not covered by core vector methods. +// - Intended for use cases such as batch processing, interoperability, and future extensions (e.g., TreeNode, BVH, SVO). +// +// Usage: +// - Use VectorHelpers for operations that go beyond standard vector element access and modification. +// - Suitable for experimental, cross-type, or performance-oriented utilities. +// +// Example operations: +// - Bulk copy, move, fill, destroy across multiple vectors or specializations. +// - Generic algorithms and batch utilities for vector-like types. +// - Integration with advanced data structures or scene systems. +// +namespace VectorHelpers +{ + // Helper for fixed-size capacity checks. + template + inline bool checkFixedCapacity(VecT* vec, U32 required) + { + using Traits = VectorTraits; + constexpr U32 COUNT = Traits::COUNT; + if constexpr (COUNT != 0) + { + if (required > COUNT) + { + AssertFatal(false, "VectorHelpers::checkFixedCapacity - operation would exceed fixed vector capacity."); + return false; + } + } + return true; + } + + // Copies elements from a buffer into a vector, up to n elements, with offset in both buffer and vector. + template + inline void fromBuffer(VecT* vec, const typename VectorTraits::value_type* buffer, U32 n, U32 vecOffset = 0, U32 bufferOffset = 0) + { + if (!vec || !buffer || n == 0) return; + using T = typename VectorTraits::value_type; + T* arr = vec->array(); + U32 len = vec->size(); + if (vecOffset >= len || bufferOffset >= n) return; + if (vecOffset + n > len) n = len - vecOffset; + if constexpr (std::is_trivially_copyable::value) + dMemcpy(arr + vecOffset, buffer + bufferOffset, n * sizeof(T)); + else if constexpr (std::is_copy_assignable::value) + for (U32 i = 0; i < n; ++i) arr[vecOffset + i] = buffer[bufferOffset + i]; + // else: do nothing, not assignable + } + + // Copies elements from a vector into a buffer, up to n elements, with offset in both vector and buffer. + template + inline void toBuffer(const VecT* vec, typename VectorTraits::value_type* buffer, U32 n, U32 vecOffset = 0, U32 bufferOffset = 0) + { + if (!vec || !buffer || n == 0) return; + using T = typename VectorTraits::value_type; + const T* arr = vec->array(); + U32 len = vec->size(); + if (vecOffset >= len || bufferOffset >= n) return; + if (vecOffset + n > len) n = len - vecOffset; + if constexpr (std::is_trivially_copyable::value) + dMemcpy(buffer + bufferOffset, arr + vecOffset, n * sizeof(T)); + else if constexpr (std::is_copy_assignable::value) + for (U32 i = 0; i < n; ++i) buffer[bufferOffset + i] = arr[vecOffset + i]; + // else: do nothing, not assignable + } + + // Moves or copies 'n' elements between a buffer and a vector at offset. + template + inline void bufferTransfer( + VecT& vec, + typename VectorTraits::value_type* buffer, + typename VectorTraits::COUNT n, + typename VectorTraits::COUNT offset, + bool toBuffer, + bool moveElements = true) + { + using T = typename VectorTraits::value_type; + using U32 = typename VectorTraits::COUNT; + U32 vecCapacity = vec.capacity(); + if (!buffer || n == 0 || offset >= vecCapacity) return; + if (offset + n > vecCapacity) n = vecCapacity - offset; + if (!toBuffer && U32(0) == 0 && offset + n > vec.size()) vec.setSize(offset + n); + T* vecArray = vec.array(); + if (!moveElements) { + if (toBuffer) toBuffer(&vec, buffer, n, offset); + else fromBuffer(&vec, buffer, n, offset); + return; + } + if constexpr (std::is_trivially_copyable::value) { + if (toBuffer) { + dMemcpy(buffer, &vecArray[offset], n * sizeof(T)); + dMemset(&vecArray[offset], 0, n * sizeof(T)); + } + else { + dMemcpy(&vecArray[offset], buffer, n * sizeof(T)); + dMemset(buffer, 0, n * sizeof(T)); + } + } + else if constexpr (std::is_move_assignable::value && std::is_default_constructible::value) { + if (toBuffer) { + for (U32 i = 0; i < n; ++i) { + buffer[i] = std::move(vecArray[offset + i]); + vecArray[offset + i] = T(); + } + } + else { + for (U32 i = 0; i < n; ++i) { + vecArray[offset + i] = std::move(buffer[i]); + buffer[i] = T(); + } + } + } + // else: do nothing, not move-assignable or not default-constructible + } + + // Merges elements from a source buffer into the end of a vector, resizing if needed. + template + inline void mergeBuffer(VecT* vec, const typename VectorTraits::value_type* src, U32 count) + { + if (!vec || !src || count == 0) return; + U32 oldSize = vec->elementCount(); + U32 newSize = oldSize + count; + if (!checkFixedCapacity(vec, newSize)) return; + vec->setSize(newSize); + auto* dest = vec->array() + oldSize; + for (U32 i = 0; i < count; ++i) + dest[i] = src[i]; + } + + // Merges elements from a source vector into the end of a target vector, resizing if needed. + template + inline void mergeVector(VecT* vec, const VecT& src) + { + if (!vec || src.size() == 0) return; + U32 oldSize = vec->elementCount(); + U32 count = src.size(); + U32 newSize = oldSize + count; + if (!checkFixedCapacity(vec, newSize)) return; + vec->setSize(newSize); + auto* dest = vec->array() + oldSize; + auto* srcPtr = src.array(); + for (U32 i = 0; i < count; ++i) + dest[i] = srcPtr[i]; + } + + // Bulk insert: shift elements once, then copy in bulk. + template + inline void insertBuffer(VecT* vec, const typename VectorTraits::value_type* src, U32 count, U32 idx) + { + if (!vec || !src || count == 0) return; + if (!checkFixedCapacity(vec, vec->elementCount() + count)) return; + + // Grow the vector to fit the new elements + U32 oldCount = vec->elementCount(); + vec->setSize(oldCount + count); + + // Move existing elements after idx up by 'count' slots + if (oldCount > idx) + move(vec, vec, oldCount - idx, idx + count, idx); + + // Copy new elements into the gap + copy(vec, src, count, idx); + } + + // Permanently redirects the vector's internal T* mArray pointer to an external pool at a given offset and range defined by start/end. + // For dynamic vectors, update mArraySize/mElementCount if needed. + // For fixed-size vectors, only updates mArray; user must ensure pool is at least COUNT + start+end elements. + // Use with caution - this is a low-level operation that can lead to instability if used incorrectly. + // The caller must ensure the pool remains valid for the vector's lifetime and that no other operations invalidate the pool. + template + inline void rebaseArrayAt(VecT* vec, typename VectorTraits::value_type* pool, U32 start, U32 end) + { + if (!vec || !pool || end <= start) return; + using Traits = VectorTraits; + constexpr U32 COUNT = Traits::COUNT; + if constexpr (COUNT == 0) + vec->set(pool + start, end - start); + else { + if ((end - start) < COUNT) return; + vec->set(pool + start, COUNT); + } + } + + // Applies a function to each element in the source vector. + // If a target vector is provided, stores the result in the target at targOffset. + // If no target is provided, applies the function in-place or for side effects. + template + inline void forEach(VecT* src, Func fn, U32 srcOffset = 0, U32 n = 0, U32 targOffset = 0, VecT* targ = NULL) + { + if (!src) return; + U32 srcLen = src->size(); + if (srcOffset >= srcLen) return; + U32 len = (n == 0) ? srcLen - srcOffset : n; + if (len == 0) return; + using T = typename VectorTraits::value_type; + T* srcArr = src->array(); + if (targ) { + U32 targLen = targ->size(); + if (targOffset >= targLen) return; + len = std::min(len, targLen - targOffset); + T* targArr = targ->array(); + for (U32 i = 0; i < len; ++i) + targArr[targOffset + i] = fn(srcArr[srcOffset + i]); + } + else { + for (U32 i = 0; i < len; ++i) + fn(srcArr[srcOffset + i]); + } + } + + // Applies a function to each pair of elements from src and targ vectors. + // Returns bool for comparison operations; otherwise, void. + template + inline auto forEachPair(VecT1* src, VecT2* targ, U32 n = 0, U32 srcOffset = 0, U32 targOffset = 0, Func fn = Func()) + { + if (!src || !targ) { + using RetType = decltype(fn(*static_cast::value_type*>(NULL), + *static_cast::value_type*>(NULL))); + if constexpr (!std::is_void_v) return RetType(); + else return; + } + U32 srcLen = src->size(), targLen = targ->size(); + if (srcOffset >= srcLen || targOffset >= targLen) { + using RetType = decltype(fn(*static_cast::value_type*>(NULL), + *static_cast::value_type*>(NULL))); + if constexpr (!std::is_void_v) return RetType(); + else return; + } + U32 len = (n == 0) ? std::min(srcLen - srcOffset, targLen - targOffset) : n; + if (len == 0) { + using RetType = decltype(fn(*static_cast::value_type*>(NULL), + *static_cast::value_type*>(NULL))); + if constexpr (!std::is_void_v) return RetType(); + else return; + } + using T = typename VectorTraits::value_type; + using RetType = decltype(fn(std::declval(), std::declval())); + T* srcArr = src->array(); + T* targArr = targ->array(); + constexpr bool isCompare = std::is_same_v; + if constexpr (isCompare) { + bool result = true; + for (U32 i = 0; i < len; ++i) + result = result && fn(srcArr[srcOffset + i], targArr[targOffset + i]); + return result; + } + else { + for (U32 i = 0; i < len; ++i) + fn(srcArr[srcOffset + i], targArr[targOffset + i]); + if constexpr (!std::is_void_v) return RetType(); + } + } + + // Constructs elements in a vector. Uses std::fill for PODs, constructInPlace for non-PODs. + template + inline void create(VecT* vec, S32 idx = -1, S32 n = 1) + { + if (!vec) return; + U32 len = vec->size(); + U32 start = (idx >= 0) ? static_cast(idx) : 0; + U32 count = (n < 1) ? (len - start) : static_cast(n); + if (count == 0 || start >= len || start + count > len) return; + using Traits = VectorTraits; + using T = typename Traits::value_type; + constexpr U32 COUNT = Traits::COUNT; + T* arr = vec->array(); + if constexpr (COUNT == 0) { + // Only construct new elements, do not destruct or move existing ones + if constexpr (!std::is_trivially_constructible::value) + for (U32 i = 0; i < count; ++i) + constructInPlace(&arr[start + i]); + else + std::fill(arr + start, arr + start + count, T()); + } + else { + if constexpr (!std::is_trivially_constructible::value) + for (U32 i = 0; i < count; ++i) + constructInPlace(&arr[start + i]); + else + std::fill(arr + start, arr + start + count, T()); + if constexpr (std::is_default_constructible::value) + std::fill(arr + start + count, arr + COUNT, T()); + } + } + + // Compacts the vector by moving elements after [start, start+count) down and updating size/backfill. + // Does NOT destruct any elements. + template + inline void compact(VecT* vec, S32 idx = -1, S32 n = 1) + { + if (!vec) return; + using T = typename VectorTraits::value_type; + using Traits = VectorTraits; + U32 len = vec->size(); + U32 start = (idx >= 0) ? static_cast(idx) : 0; + U32 count = (n < 1) ? (len - start) : static_cast(n); + if (count == 0 || start >= len || start + count > len) return; + T* arr = vec->array(); + + U32 tail = len - (start + count); + if (tail > 0) + { + if constexpr (std::is_trivially_copyable::value) + { + dMemmove(&arr[start], &arr[start + count], tail * sizeof(T)); + // Reset tail region to default value + std::fill(arr + (start + tail), arr + (start + tail + count), T()); + } + else if constexpr (std::is_default_constructible::value) + { + for (U32 i = 0; i < tail; ++i) + arr[start + i] = std::move(arr[start + count + i]); + // Reset tail region to default value + for (U32 i = start + tail; i < start + tail + count; ++i) + constructInPlace(&arr[i]); + } + // else: do nothing, not default-constructible or not assignable + } + + if constexpr (Traits::COUNT == 0) + { + // Dynamic vector: update size + vec->setElementCount(len - count); + } + else + { + // Fixed-size: backfill tail + for (U32 i = len - count; i < len; ++i) + constructInPlace(&arr[i]); + } + } + + // Destroys elements in a vector, then compacts. + // Destructs elements in range, then calls compact to move and update size/backfill. + template + inline void destroy(VecT* vec, S32 idx = -1, S32 n = 1) + { + if (!vec) return; + U32 len = vec->size(); + U32 start = (idx >= 0) ? static_cast(idx) : 0; + U32 count = (n < 1) ? (len - start) : static_cast(n); + if (count == 0 || start >= len || start + count > len) return; + using T = typename VectorTraits::value_type; + T* arr = vec->array(); + + for (U32 i = 0; i < count; ++i) + { + if (arr && (start + i) < len) + { + destructInPlace(&arr[start + i]); + } + vec->setElementCount(len - count); + } + } + + // Removes elements matching a predicate + // Dynamic vectors: compacts and shrinks. + // Fixed-size vectors: destroys and backfills each matching element with type default. + template + inline void removeIf(Vector* vec, Func fn, U32 offset = 0, U32 n = 0) + { + if (!vec) return; + U32 len = vec->size(); + if (offset >= len) return; + U32 end = (n == 0) ? len : getMin(len, offset + n); + if (end <= offset) return; + T* arr = vec->array(); + if constexpr (COUNT == 0) { + U32 write = offset; + for (U32 read = offset; read < end; ++read) + if (!fn(arr[read])) { + if (write != read) arr[write] = std::move(arr[read]); + ++write; + } + VectorHelpers::destroy(vec, write, len - write); + vec->setSize(write); + } + else { + // Compact and backfill using destroy for fixed-size + U32 write = offset; + for (U32 read = offset; read < end; ++read) + if (!fn(arr[read])) { + if (write != read) arr[write] = std::move(arr[read]); + ++write; + } + VectorHelpers::destroy(vec, write, len - write); + } + } + + // Counts elements matching a predicate. + template + inline U32 countIf(const Vector* vec, Func fn) + { + if (!vec) return 0; + U32 len = vec->size(); + if (len == 0) return 0; + U32 n = 0; + const T* arr = vec->array(); + for (U32 i = 0; i < len; ++i) + if (fn(arr[i])) ++n; + return n; + } + + /// Returns true if any element matches the predicate. + template + inline bool anyOf(const Vector* vec, Func fn) + { + if (!vec) return false; + U32 len = vec->size(); + if (len == 0) return false; + const T* arr = vec->array(); + for (U32 i = 0; i < len; ++i) + if (fn(arr[i])) return true; + return false; + } + + /// Returns true if all elements match the predicate. + template + inline bool allOf(const Vector* vec, Func fn) + { + if (!vec) return false; + U32 len = vec->size(); + if (len == 0) return false; + const T* arr = vec->array(); + for (U32 i = 0; i < len; ++i) + if (!fn(arr[i])) return false; + return true; + } + + // Copies n elements from src to dst vector. + // Supports cross-specialization and optimized for PODs. + template + inline void copy(TargVecT* targ, const SrcVecT* src, U32 n = 0, U32 targOffset = 0, U32 srcOffset = 0) + { + using T = typename VectorTraits::value_type; + U32 targLen = targ->size(); + U32 len = n ? n : targLen - targOffset; + if (len == 0) return; + if constexpr (std::is_trivially_copyable::value) + std::memmove(targ->array() + targOffset, src + srcOffset, len * sizeof(T)); + else + for (U32 i = 0; i < len; ++i) + targ->array()[targOffset + i] = src[srcOffset + i]; + } + + // Moves n elements from src to dst vector. + // Supports cross-specialization and optimized for PODs. + template + inline void move(TargVecT* targ, SrcVecT* src, U32 n = 0, U32 targOffset = 0, U32 srcOffset = 0) + { + using T = typename VectorTraits::value_type; + constexpr U32 COUNT = VectorTraits::COUNT; + U32 targLen = targ->size(); + U32 len = n ? n : targLen - targOffset; + if (len == 0) return; + if constexpr (std::is_trivially_copyable::value) + std::memmove(targ->array() + targOffset, src->array() + srcOffset, len * sizeof(T)); + else if constexpr (std::is_move_assignable::value && std::is_move_constructible::value) + for (U32 i = 0; i < len; ++i) { + targ->array()[targOffset + i] = std::move(src->array()[srcOffset + i]); + if constexpr (COUNT != 0) + constructInPlace(&src->array()[srcOffset + i]); + else + src->array()[srcOffset + i] = T(); + } + else if constexpr (std::is_copy_assignable::value) + for (U32 i = 0; i < len; ++i) { + targ->array()[targOffset + i] = src->array()[srcOffset + i]; + if constexpr (COUNT != 0) + constructInPlace(&src->array()[srcOffset + i]); + else + src->array()[srcOffset + i] = T(); + } + else + static_assert(std::is_copy_assignable::value || std::is_move_assignable::value, + "VectorHelpers::move: T must be move- or copy-assignable."); + } + + // Fills n elements starting at offset with value. + template + inline void fill(VecT* vec, const typename VectorTraits::value_type& val, U32 n = 0, U32 offset = 0) + { + using Traits = VectorTraits; + using T = typename Traits::value_type; + U32 len = vec->size(); + if (n == 0) n = len - offset; + if (offset >= len || n == 0) return; + if constexpr (std::is_trivially_copyable::value) { + if constexpr (std::is_integral::value || std::is_floating_point::value || std::is_pointer::value) { + if (val == T()) { + std::memset(vec->array() + offset, 0, n * sizeof(T)); + return; + } + } + for (U32 i = 0; i < n; ++i) + vec->array()[offset + i] = val; + } + else if constexpr (std::is_copy_assignable::value) + for (U32 i = 0; i < n; ++i) + vec->array()[offset + i] = val; + // else: not assignable, do nothing + } + + // Assigns n elements from src to targ vector, handling overlap for non-PODs. + template + inline void assign(TargVecT* targ, const SrcVecT* src, U32 n = 0, U32 targOffset = 0, U32 srcOffset = 0) + { + using Traits = VectorTraits; + using T = typename Traits::value_type; + constexpr bool isPod = std::is_trivially_copyable::value; + U32 targLen = targ->size(), srcLen = src->size(); + if (n == 0 || targOffset + n > targLen || srcOffset + n > srcLen) + n = getMin(targLen - targOffset, srcLen - srcOffset); + if constexpr (Traits::COUNT == 0) + if (targOffset + n > targ->elementCount()) + targ->setSize(targOffset + n); + if (n == 0) return; + if constexpr (isPod) + std::memmove(targ->array() + targOffset, src->array() + srcOffset, n * sizeof(T)); + else if constexpr (std::is_copy_assignable::value) { + if (targ == src && std::abs((int)targOffset - (int)srcOffset) < (int)n) { + std::unique_ptr temp(new T[n]); + toBuffer(src, temp.get(), n, srcOffset); + fromBuffer(targ, temp.get(), n, targOffset); + } + else { + fromBuffer(targ, src->array() + srcOffset, n, targOffset); + } + } + // else: not copy-assignable, do nothing + } + + // Swaps n elements between two ranges in one or two vectors. + template + inline void swap(VecT* src, U32 srcOffset, U32 targOffset, U32 n = 0, OtherVecT* targ = NULL) + { + using T = typename VectorTraits::value_type; + U32 srcLen = src->size(), targLen = targ ? targ->size() : srcLen; + U32 len = (n == 0) ? getMin(srcLen - srcOffset, targLen - targOffset) : n; + T* srcArr = src->array(); + T* targArr = targ ? targ->array() : src->array(); + if constexpr (std::is_move_constructible::value && std::is_move_assignable::value) { + for (U32 i = 0; i < len; ++i) + std::swap(srcArr[srcOffset + i], targArr[targOffset + i]); + } + else if constexpr (std::is_copy_constructible::value && std::is_copy_assignable::value) { + // Fallback: use a temp buffer + std::unique_ptr temp(new T[len]); + for (U32 i = 0; i < len; ++i) + temp[i] = srcArr[srcOffset + i]; + for (U32 i = 0; i < len; ++i) + srcArr[srcOffset + i] = targArr[targOffset + i]; + for (U32 i = 0; i < len; ++i) + targArr[targOffset + i] = temp[i]; + } + // else: do nothing, not swappable + } + + // Reverses the order of elements in the vector or a range. + template + inline void reverse(VecT* vec, U32 n = 0, U32 offset = 0) + { + using T = typename VectorTraits::value_type; + U32 len = vec->size(); + if (offset >= len) return; + if (n == 0) n = len - offset; + if (n < 2) return; + T* arr = vec->array(); + if constexpr (std::is_move_constructible::value && std::is_move_assignable::value) { + U32 i = offset, j = offset + n - 1; + while (i < j) { + std::swap(arr[i], arr[j]); + ++i; --j; + } + } + else if constexpr (std::is_copy_constructible::value && std::is_copy_assignable::value) { + // Fallback: use a temp buffer + std::unique_ptr temp(new T[n]); + for (U32 i = 0; i < n; ++i) + temp[i] = arr[offset + n - 1 - i]; + for (U32 i = 0; i < n; ++i) + arr[offset + i] = temp[i]; + } + // else: do nothing, not reversible + } + + // Compares a range of elements between two vectors. + template + inline bool compare(const VecT* src, const OtherVecT* targ, U32 n = 0, U32 srcOffset = 0, U32 targOffset = 0) + { + U32 srcLen = src->size(), targLen = targ->size(); + if (srcOffset >= srcLen || targOffset >= targLen) return false; + if (n == 0) n = getMin(srcLen - srcOffset, targLen - targOffset); + auto arr1 = src->array(), arr2 = targ->array(); + for (U32 i = 0; i < n; ++i) + if (!(arr1[srcOffset + i] == arr2[targOffset + i])) return false; + return true; + } + + // Finds the index of a value in a vector or range. + template + inline S32 find(const VecT* vec, U32 n, const typename VecT::value_type& value, U32 offset = 0) + { + U32 len = vec->size(); + if (offset >= len) return -1; + if (n <= 0) n = len - offset; //consider n=0 or n=-1 as "search to end" + auto arr = vec->array(); + for (U32 i = 0; i < n; ++i) + if (arr[offset + i] == value) return static_cast(offset + i); + return -1; + } + + // Clears or resets a single element at a given index. + template + inline void clearElement(VecT* vec, U32 idx) + { + using T = typename VecT::value_type; + U32 len = vec->size(); + if (idx >= len) return; + if constexpr (std::is_trivially_copyable::value) + std::fill(vec->array() + idx, vec->array() + idx + 1, T()); + else if constexpr (std::is_default_constructible::value && std::is_copy_assignable::value) + vec->array()[idx] = T(); + // else: do nothing, not default-constructible or not assignable + } + + template + inline void moveWithin(VecT* vec, typename VecT::value_type* src, U32 n, U32 destOffset) + { + using T = typename VectorTraits::value_type; + if (!vec || !src || n == 0) return; + T* arr = vec->array(); + if constexpr (std::is_trivially_copyable::value) { + std::memmove(arr + destOffset, src, n * sizeof(T)); + } + else if (arr + destOffset > src && arr + destOffset < src + n) { + for (S32 i = n - 1; i >= 0; --i) + arr[destOffset + i] = std::move(src[i]); + } + else { + for (U32 i = 0; i < n; ++i) + arr[destOffset + i] = std::move(src[i]); + } + } + + template + inline typename VectorTraits::value_type* data(VecT* vec) + { + return vec ? vec->array() : NULL; + } + + template + inline const typename VectorTraits::value_type* data(const VecT* vec) + { + return vec ? vec->array() : NULL; + } + +} + // ============================================================================= /// A dynamic array template class. /// @@ -62,31 +790,39 @@ extern bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, /// reserve() method. /// /// @nosubgrouping -template +template> class Vector { friend class VectorFieldEngineExport; - protected: - U32 mElementCount; ///< Number of elements currently in the Vector. - U32 mArraySize; ///< Number of elements allocated for the Vector. - T* mArray; ///< Pointer to the Vector elements. +protected: + mutable typename Allocator::State mState; + T* mArray; ///< Pointer to the Vector elements. #ifdef TORQUE_DEBUG_GUARD const char* mFileAssociation; - U32 mLineAssociation; + U32 mLineAssociation; #endif - bool resize(U32); // resizes, but does no construction/destruction - void destroy(U32 start, U32 end); ///< Destructs elements from start to end-1 + bool resize(U32); // resizes, and does construction/destruction as apropriate + void destroy(U32 start, U32 end); ///< Destructs elements from start to end-1 void construct(U32 start, U32 end); ///< Constructs elements from start to end-1 - void construct(U32 start, U32 end, const T* array); - public: + void construct(U32 start, U32 end, const T* array); + +public: + /// @name Constructors/Destructor + /// @{ + void zeroState(); + /// Constructs a vector with an optional initial size. Vector(const U32 initialSize = 0); Vector(const U32 initialSize, const char* fileName, const U32 lineNum); Vector(const char* fileName, const U32 lineNum); Vector(const Vector&); ~Vector(); + /// allocator state access + typename Allocator::State& state() { return mState; } + const typename Allocator::State& state() const { return mState; } + #ifdef TORQUE_DEBUG_GUARD void setFileAssociation(const char* file, const U32 line); #endif @@ -95,17 +831,15 @@ class Vector /// @{ typedef T value_type; - typedef T& reference; + typedef T& reference; typedef const T& const_reference; - - typedef T* iterator; + typedef T* iterator; typedef const T* const_iterator; - typedef S32 difference_type; - typedef U32 size_type; + typedef S32 difference_type; + typedef U32 size_type; + typedef difference_type(QSORT_CALLBACK* compare_func)(const T* a, const T* b); - typedef difference_type (QSORT_CALLBACK *compare_func)(const T *a, const T *b); - - Vector& operator=(const Vector& p); + Vector& operator=(const Vector& p); iterator begin(); const_iterator begin() const; @@ -119,24 +853,23 @@ class Vector void insert(iterator, const T&); void erase(iterator); - T& front(); + T& front(); const T& front() const; - T& back(); + T& back(); const T& back() const; void push_front(const T&); void push_back(const T&); U32 push_front_unique(const T&); U32 push_back_unique(const T&); - S32 find_next( const T&, U32 start = 0 ) const; + S32 find_next(const T&, U32 start = 0) const; void pop_front(); void pop_back(); T& operator[](U32); const T& operator[](U32) const; - - T& operator[](S32 i) { return operator[](U32(i)); } - const T& operator[](S32 i ) const { return operator[](U32(i)); } + T& operator[](S32 i) { return operator[](U32(i)); } + const T& operator[](S32 i) const { return operator[](U32(i)); } void reserve(U32); U32 capacity() const; @@ -147,7 +880,7 @@ class Vector /// @{ U32 memSize() const; - T* address() const; + T* address() const; U32 setSize(U32); void increment(); void decrement(); @@ -162,528 +895,662 @@ class Vector void clear(); void compact(); void sort(compare_func f); - void fill( const T& value ); + void fill(const T& value); /// Finds the first matching element and erases it. /// @return Returns true if a match is found. - bool remove( const T& ); + bool remove(const T&); T& first(); T& last(); const T& first() const; const T& last() const; - void set(void * addr, U32 sz); + void set(void* addr, U32 sz); /// Appends the content of the vector to this one. - void merge( const Vector &p ); + void merge(const Vector& p); /// Appends the content of the array to the vector. /// /// @param addr A pointer to the first item of the array to merge. /// @param count The number of elements in the array to merge. /// - void merge( const T *addr, U32 count ); + void merge(const T* addr, U32 count); // Reverses the order of elements. void reverse(); /// @} + + // Modern/extra methods (from tVector.h, not in original, but keep at end) + void insert(const T* src, U32 count, U32 idx); + void fill(const T& value, U32 count); + void fill(const T& value, U32 count, U32 offset); + + template + void removeIf(Func pred); + + template + void forEach(Func fn); + + template + U32 countIf(Func fn) const; + + template + bool anyOf(Func fn) const; + + template + bool allOf(Func fn) const; + + void reverse(U32 offset, U32 count); + + template + void swap(Vector& other); + template + void swap(Vector& other, U32 thisOffset, U32 otherOffset, U32 count); + + template + bool compare(const Vector& other) const; + template + bool compare(const Vector& other, U32 thisOffset, U32 otherOffset, U32 count) const; + + void copy(const T* src); + + template + void assign(const SrcVec* src, U32 n = 0, U32 targOffset = 0, U32 srcOffset = 0); + + template + void moveFrom(SrcVec& src); + + // Returns the number of elements currently in the Vector. + inline U32 elementCount() const { return COUNT == 0 ? mState.elementCount() : COUNT; } + // Returns the number of elements allocated for the Vector. + inline U32 arraySize() const { return COUNT == 0 ? mState.arraySize() : COUNT; } + // Returns pointer to the Vector elements. + inline T* array() const { return mArray; } + + inline void setElementCount(U32 count) + { + AssertFatal(count >= 0, "setElementCount: can't set a negative element count!"); + AssertFatal(count <= arraySize(), "setElementCount: count exceeds array size!"); + mState.setElementCount(count); + + } + inline void setArraySize(U32 size) + { + AssertFatal(size >= 0, "setElementCount: can't set a negative array size!"); + mState.setArraySize(size); + AssertFatal(elementCount() <= arraySize(), "elementCount exceeds arraySize after setArraySize!"); + } }; class VectorFieldEngineExport { public: - template - static EngineFieldTable::Field getElementCountField() - { - typedef Vector ThisType; - return _FIELD(mElementCount, elementCount, 1, ""); - }; - template - static EngineFieldTable::Field getArraySizeField() - { - typedef Vector ThisType; - return _FIELD(mArraySize, arraySize, 1, ""); - }; template static EngineFieldTable::Field getArrayField() { typedef Vector ThisType; return _FIELD(mArray, array, 1, ""); }; + + // given EngineFieldTable::Field _sFields[] = { + // always stops at the first found NULL, + // keep strict order of most to least likely to be used fields, + // and put the least likely to be used ones at the end + // see _IMPLEMENT_STRUCT and _END_IMPLEMENT_STRUCT for further details + + template + static EngineFieldTable::Field getElementCountField() + { + typedef Vector ThisType; + if constexpr (VectorTraits::COUNT == 0) + { + return EngineFieldTable::Field{ + "elementCount", "", 1, + TYPE(((ThisType*)16)->mState.mElementCount), + FIELDOFFSET(mState.mElementCount) + }; + } + else + return { NULL }; //trash + }; + template + static EngineFieldTable::Field getArraySizeField() + { + typedef Vector ThisType; + + if constexpr (VectorTraits::COUNT == 0) + { + return EngineFieldTable::Field{ + "arraySize", "", 1, + TYPE(((ThisType*)16)->mState.mArraySize), + FIELDOFFSET(mState.mArraySize) + }; + } + else + return { NULL }; + }; }; -template inline Vector::~Vector() +template +inline Vector::~Vector() { + // Destroys all elements and deallocates memory. clear(); - dFree(mArray); + if constexpr (COUNT == 0) + { + if (mArray) + Allocator::deallocate(mArray); + mArray = NULL; + } + mArray = NULL; } - -template inline Vector::Vector(const U32 initialSize) +template +inline void Vector::zeroState() { #ifdef TORQUE_DEBUG_GUARD mFileAssociation = NULL; mLineAssociation = 0; #endif - - mArray = 0; - mElementCount = 0; - mArraySize = 0; - if(initialSize) - reserve(initialSize); + mArray = NULL; + setElementCount(0); + setArraySize(0); + if constexpr (COUNT != 0) + { + mArray = Allocator::allocate(COUNT); + construct(0, COUNT); + } } -template inline Vector::Vector(const U32 initialSize, - const char* fileName, - const U32 lineNum) +template +inline Vector::Vector(const U32 initialSize) { + zeroState(); + +#ifdef TORQUE_DEBUG_GUARD + mFileAssociation = NULL; + mLineAssociation = 0; +#endif + if (initialSize) + reserve(initialSize); +} +template +inline Vector::Vector(const char* fileName, + const U32 lineNum) +{ + zeroState(); #ifdef TORQUE_DEBUG_GUARD mFileAssociation = fileName; mLineAssociation = lineNum; #else -// TORQUE_UNUSED(fileName); -// TORQUE_UNUSED(lineNum); + // TORQUE_UNUSED(fileName); + // TORQUE_UNUSED(lineNum); #endif - - mArray = 0; - mElementCount = 0; - mArraySize = 0; - if(initialSize) - reserve(initialSize); } -template inline Vector::Vector(const char* fileName, - const U32 lineNum) -{ -#ifdef TORQUE_DEBUG_GUARD - mFileAssociation = fileName; - mLineAssociation = lineNum; -#else -// TORQUE_UNUSED(fileName); -// TORQUE_UNUSED(lineNum); -#endif - - mArray = 0; - mElementCount = 0; - mArraySize = 0; -} - -template inline Vector::Vector(const Vector& p) +template +inline Vector::Vector(const Vector& p) { + if (this == &p) + return; + zeroState(); #ifdef TORQUE_DEBUG_GUARD mFileAssociation = p.mFileAssociation; mLineAssociation = p.mLineAssociation; #endif - - mArray = 0; - resize(p.mElementCount); - construct(0, p.mElementCount, p.mArray); + if constexpr (COUNT == 0) + { + resize(p.elementCount()); + construct(0, p.elementCount(), p.mArray); + } + else + { + construct(0, COUNT, p.mArray); + } } - - #ifdef TORQUE_DEBUG_GUARD -template inline void Vector::setFileAssociation(const char* file, - const U32 line) +template +inline void Vector::setFileAssociation(const char* file, + const U32 line) { mFileAssociation = file; mLineAssociation = line; } #endif -template inline void Vector::destroy(U32 start, U32 end) // destroys from start to end-1 + +template +inline void Vector::destroy(U32 start, U32 end) // destroys from start to end-1 { // This check is a little generous as we can legitimately get (0,0) as // our parameters... so it won't detect every invalid case but it does // remain simple. - AssertFatal(start <= mElementCount && end <= mElementCount, "Vector::destroy - out of bounds start/end."); - + AssertFatal(start <= elementCount() && end <= elementCount(), "Vector::destroy - out of bounds start/end."); // destroys from start to end-1 - while(start < end) + while (start < end) destructInPlace(&mArray[start++]); } -template inline void Vector::construct(U32 start, U32 end) // destroys from start to end-1 +template +inline void Vector::construct(U32 start, U32 end) // allocates from start to end-1 { - AssertFatal(start <= mElementCount && end <= mElementCount, "Vector::construct - out of bounds start/end."); - while(start < end) + AssertFatal(start <= elementCount() && end <= elementCount(), "Vector::construct - out of bounds start/end."); + while (start < end) constructInPlace(&mArray[start++]); } -template inline void Vector::construct(U32 start, U32 end, const T* array) // destroys from start to end-1 +template +inline void Vector::construct(U32 start, U32 end, const T* array) // allocates from start to end-1 { - AssertFatal(start <= mElementCount && end <= mElementCount, "Vector::construct - out of bounds start/end."); - while(start < end) - { - constructInPlace(&mArray[start], &array[start]); - start++; - } + AssertFatal(start <= elementCount() && end <= elementCount(), "Vector::construct - out of bounds start/end."); + while (start < end) + { + constructInPlace(&mArray[start], &array[start]); + start++; + } } -template inline U32 Vector::memSize() const +template +inline U32 Vector::memSize() const // reserved size in bytes { return capacity() * sizeof(T); } -template inline T* Vector::address() const +template +inline T* Vector::address() const { return mArray; } -template inline U32 Vector::setSize(U32 size) +template +inline U32 Vector::setSize(U32 size) { - const U32 oldSize = mElementCount; - - if(size > mElementCount) + // Sets the number of elements in the vector. + // If increasing, constructs new elements. If decreasing, destructs removed elements. + U32 oldSize = elementCount(); + if constexpr (COUNT == 0) { - if (size > mArraySize) - resize(size); - - // Set count first so we are in a valid state for construct. - mElementCount = size; - construct(oldSize, size); + if (size > oldSize) + { + if (size > arraySize()) + resize(size); + setElementCount(size); + construct(oldSize, size); + } + else if (size < oldSize) + { + destroy(size, oldSize); + setElementCount(size); + } + // else, size == oldSize, do nothing + return elementCount(); } - else if(size < mElementCount) - { - destroy(size, oldSize); - mElementCount = size; - } - - return mElementCount; -} - -template inline void Vector::increment() -{ - if(mElementCount == mArraySize) - resize(mElementCount + 1); else - mElementCount++; - constructInPlace(&mArray[mElementCount - 1]); + { + // For fixed-size vectors, just return the count + return COUNT; + } } -template inline void Vector::decrement() +template +inline void Vector::increment() { - AssertFatal(mElementCount != 0, "Vector::decrement - cannot decrement zero-length vector."); - mElementCount--; - destructInPlace(&mArray[mElementCount]); + setSize(elementCount() + 1); } -template inline void Vector::increment(U32 delta) +template +inline void Vector::decrement() { - U32 count = mElementCount; - if ((mElementCount += delta) > mArraySize) - resize(mElementCount); - construct(count, mElementCount); + AssertFatal(elementCount(), "Vector::decrement - cannot decrement zero-length vector."); + setSize(elementCount() - 1); } -template inline void Vector::decrement(U32 delta) +template +inline void Vector::increment(U32 delta) { - AssertFatal(mElementCount != 0, "Vector::decrement - cannot decrement zero-length vector."); - - const U32 count = mElementCount; + setSize(elementCount() + delta); +} +template +inline void Vector::decrement(U32 delta) +{ + AssertFatal(elementCount(), "Vector::decrement - cannot decrement zero-length vector."); // Determine new count after decrement... - U32 newCount = mElementCount; - if (mElementCount > delta) - newCount -= delta; + U32 newCount = (elementCount() > delta) ? elementCount() - delta : 0; + setSize(newCount); +} + +template +inline void Vector::insert(U32 index) +{ + AssertFatal(index <= elementCount(), "Vector::insert - out of bounds index."); + if constexpr (COUNT == 0) + { + setSize(elementCount() + 1); + dMemmove(&mArray[index + 1], &mArray[index], dsize_t(elementCount() - index - 1) * sizeof(value_type)); + constructInPlace(&mArray[index]); + } else - newCount = 0; - - // Destruct removed items... - destroy(newCount, count); - - // Note new element count. - mElementCount = newCount; + { + /// DO RIGHT! + } } -template inline void Vector::insert(U32 index) +template +inline void Vector::insert(U32 index, const T& x) { - AssertFatal(index <= mElementCount, "Vector::insert - out of bounds index."); + if constexpr (COUNT == 0) + { - if(mElementCount == mArraySize) - resize(mElementCount + 1); + insert(index); + mArray[index] = x; + } else - mElementCount++; + { + if (index < COUNT) + { - dMemmove(&mArray[index + 1], - &mArray[index], - dsize_t(mElementCount - index - 1) * sizeof(value_type)); - - constructInPlace(&mArray[index]); + insert(index); + (*this)[index] = x; + } + } } -template inline void Vector::insert(U32 index,const T& x) +template +inline void Vector::erase(U32 index) { - insert(index); - mArray[index] = x; -} - -template inline void Vector::erase(U32 index) -{ - AssertFatal(index < mElementCount, "Vector::erase - out of bounds index!"); + AssertFatal(index < elementCount(), "Vector::erase - out of bounds index!"); destructInPlace(&mArray[index]); - if (index < (mElementCount - 1)) + if (index < (elementCount() - 1)) { dMemmove(&mArray[index], &mArray[index + 1], - dsize_t(mElementCount - index - 1) * sizeof(value_type)); + dsize_t(elementCount() - index - 1) * sizeof(value_type)); } - - mElementCount--; + setElementCount(elementCount() - 1); } -template inline bool Vector::remove( const T& x ) +template +inline bool Vector::remove(const T& x) { iterator i = begin(); while (i != end()) { if (*i == x) { - erase( i ); + erase(i); return true; } i++; } - return false; } -template inline void Vector::erase(U32 index, U32 count) +template +inline void Vector::erase(U32 index, U32 count) { - AssertFatal(index < mElementCount, "Vector::erase - out of bounds index!"); + AssertFatal(index < elementCount(), "Vector::erase - out of bounds index!"); AssertFatal(count > 0, "Vector::erase - count must be greater than zero!"); - AssertFatal(index+count <= mElementCount, "Vector::erase - out of bounds count!"); + AssertFatal(index + count <= elementCount(), "Vector::erase - out of bounds count!"); + destroy(index, index + count); - destroy( index, index+count ); - - dMemmove( &mArray[index], - &mArray[index + count], - dsize_t(mElementCount - index - count) * sizeof(value_type)); - - mElementCount -= count; + dMemmove(&mArray[index], + &mArray[index + count], + dsize_t(elementCount() - index - count) * sizeof(value_type)); + setElementCount(elementCount() - count); } -template inline void Vector::erase_fast(U32 index) +template +inline void Vector::erase_fast(U32 index) { - AssertFatal(index < mElementCount, "Vector::erase_fast - out of bounds index."); + AssertFatal(index < elementCount(), "Vector::erase_fast - out of bounds index."); // CAUTION: this operator does NOT maintain list order // Copy the last element into the deleted 'hole' and decrement the // size of the vector. destructInPlace(&mArray[index]); - if (index < (mElementCount - 1)) - dMemmove(&mArray[index], &mArray[mElementCount - 1], sizeof(value_type)); - mElementCount--; + if (index != elementCount() - 1) + mArray[index] = mArray[elementCount() - 1]; + setElementCount(elementCount() - 1); } -template inline bool Vector::contains(const T& x) const +template +inline bool Vector::contains(const T& x) const { - const_iterator i = begin(); - while (i != end()) - { - if (*i == x) - return true; - - i++; - } - - return false; + const_iterator i = begin(); + while (i != end()) + { + if (*i == x) + return true; + i++; + } + return false; } -template< class T > inline void Vector< T >::fill( const T& value ) +template +inline void Vector::fill(const T& value) { - for( U32 i = 0; i < size(); ++ i ) - mArray[ i ] = value; + for (U32 i = 0; i < size(); ++i) + mArray[i] = value; } -template inline T& Vector::first() +template +inline T& Vector::first() { - AssertFatal(mElementCount != 0, "Vector::first - Error, no first element of a zero sized array!"); + AssertFatal(elementCount(), "Vector::first - Error, no first element of a zero sized array!"); return mArray[0]; } -template inline const T& Vector::first() const +template +inline const T& Vector::first() const { - AssertFatal(mElementCount != 0, "Vector::first - Error, no first element of a zero sized array! (const)"); + AssertFatal(elementCount(), "Vector::first - Error, no first element of a zero sized array! (const)"); return mArray[0]; } -template inline T& Vector::last() +template +inline T& Vector::last() { - AssertFatal(mElementCount != 0, "Vector::last - Error, no last element of a zero sized array!"); - return mArray[mElementCount - 1]; + // Returns reference to last element. Asserts if empty. + AssertFatal(elementCount(), "Vector::last - Error, no last element of a zero sized array!"); + return mArray[elementCount() - 1]; } -template inline const T& Vector::last() const +template +inline const T& Vector::last() const { - AssertFatal(mElementCount != 0, "Vector::last - Error, no last element of a zero sized array! (const)"); - return mArray[mElementCount - 1]; + AssertFatal(elementCount(), "Vector::last - Error, no last element of a zero sized array! (const)"); + return mArray[elementCount() - 1]; } -template inline void Vector::clear() +template +inline void Vector::clear() { - destroy(0, mElementCount); - mElementCount = 0; + destroy(0, elementCount()); + setElementCount(0); } -template inline void Vector::compact() +template +inline void Vector::compact() { - resize(mElementCount); + resize(elementCount()); } -typedef S32 (QSORT_CALLBACK *qsort_compare_func)(const void *, const void *); +typedef S32(QSORT_CALLBACK* qsort_compare_func)(const void*, const void*); -template inline void Vector::sort(compare_func f) +template +inline void Vector::sort(compare_func f) { - qsort(address(), size(), sizeof(T), (qsort_compare_func) f); + qsort(address(), size(), sizeof(T), (qsort_compare_func)f); } //----------------------------------------------------------------------------- -template inline Vector& Vector::operator=(const Vector& p) + +template +inline Vector& Vector::operator=(const Vector& p) { - if(mElementCount > p.mElementCount) + if (this == &p) + return *this; + + if (elementCount() > p.elementCount()) { - destroy(p.mElementCount, mElementCount); + destroy(p.elementCount(), elementCount()); } - - U32 count = getMin( mElementCount, p.mElementCount ); + + U32 count = getMin(elementCount(), p.elementCount()); U32 i; - for( i=0; i < count; i++ ) + for (i = 0; i < count; i++) { mArray[i] = p.mArray[i]; } - - resize( p.mElementCount ); - - if( i < p.mElementCount ) + + resize(p.elementCount()); + + if (i < p.elementCount()) { - construct(i, p.mElementCount, p.mArray); + construct(i, p.elementCount(), p.mArray); } return *this; } -template inline typename Vector::iterator Vector::begin() +template +inline typename Vector::iterator Vector::begin() { return mArray; } -template inline typename Vector::const_iterator Vector::begin() const +template +inline typename Vector::const_iterator Vector::begin() const { return mArray; } -template inline typename Vector::iterator Vector::end() +template +inline typename Vector::iterator Vector::end() { - return mArray + mElementCount; + return mArray + elementCount(); } -template inline typename Vector::const_iterator Vector::end() const +template +inline typename Vector::const_iterator Vector::end() const { - return mArray +mElementCount; + return mArray + elementCount(); } -template inline S32 Vector::size() const +template +inline S32 Vector::size() const { - return (S32)mElementCount; + if constexpr (COUNT == 0) + return (S32)elementCount(); + else + return (S32)COUNT; } -template inline bool Vector::empty() const +template +inline bool Vector::empty() const { - return (mElementCount == 0); + return size() == 0; } -template inline void Vector::insert(iterator p,const T& x) +template +inline void Vector::insert(iterator p, const T& x) { - U32 index = (U32) (p - mArray); + U32 index = (U32)(p - mArray); insert(index); mArray[index] = x; } -template inline void Vector::erase(iterator q) +template +inline void Vector::erase(iterator q) { erase(U32(q - mArray)); } -template inline void Vector::erase_fast(iterator q) +template +inline void Vector::erase_fast(iterator q) { erase_fast(U32(q - mArray)); } -template inline T& Vector::front() +template +inline typename Vector::reference Vector::front() { return *begin(); } -template inline const T& Vector::front() const +template +inline typename Vector::const_reference Vector::front() const { return *begin(); } -template inline T& Vector::back() +template +inline typename Vector::reference Vector::back() { - AssertFatal(mElementCount != 0, "Vector::back - cannot access last element of zero-length vector."); - return *(end()-1); + AssertFatal(elementCount(), "Vector::back - cannot access last element of zero-length vector."); + return *(end() - 1); } -template inline const T& Vector::back() const +template +inline typename Vector::const_reference Vector::back() const { - AssertFatal(mElementCount != 0, "Vector::back - cannot access last element of zero-length vector."); - return *(end()-1); + AssertFatal(elementCount(), "Vector::back - cannot access last element of zero-length vector."); + return *(end() - 1); } -template inline void Vector::push_front(const T& x) +template +inline void Vector::push_front(const T& x) { insert(0); mArray[0] = x; } -template inline void Vector::push_back(const T& x) +template +inline void Vector::push_back(const T& x) { increment(); - mArray[mElementCount - 1] = x; + mArray[elementCount() - 1] = x; } -template inline U32 Vector::push_front_unique(const T& x) +template +inline U32 Vector::push_front_unique(const T& x) { S32 index = find_next(x); - if (index == -1) { index = 0; - insert(index); mArray[index] = x; } - return index; } -template inline U32 Vector::push_back_unique(const T& x) +template +inline U32 Vector::push_back_unique(const T& x) { S32 index = find_next(x); - if (index == -1) { increment(); - - index = mElementCount - 1; + index = elementCount() - 1; mArray[index] = x; } - return index; } -template inline S32 Vector::find_next( const T& x, U32 start ) const +template +inline S32 Vector::find_next(const T& x, U32 start) const { S32 index = -1; - - if (start < mElementCount) + if (start < elementCount()) { - for (U32 i = start; i < mElementCount; i++) + for (U32 i = start; i < elementCount(); i++) { if (mArray[i] == x) { @@ -692,119 +1559,396 @@ template inline S32 Vector::find_next( const T& x, U32 start ) const } } } - return index; } -template inline void Vector::pop_front() +template +inline void Vector::pop_front() { - AssertFatal(mElementCount != 0, "Vector::pop_front - cannot pop the front of a zero-length vector."); + AssertFatal(elementCount(), "Vector::pop_front - cannot pop the front of a zero-length vector."); erase(U32(0)); } -template inline void Vector::pop_back() +template +inline void Vector::pop_back() { - AssertFatal(mElementCount != 0, "Vector::pop_back - cannot pop the back of a zero-length vector."); + AssertFatal(elementCount(), "Vector::pop_back - cannot pop the back of a zero-length vector."); decrement(); } -template inline T& Vector::operator[](U32 index) +template +inline T& Vector::operator[](U32 index) { - AssertFatal(index < mElementCount, avar("Vector::operator[%i/%i] - out of bounds array access!", index, mElementCount)); + AssertFatal(mArray != NULL, "Vector::operator[] - mArray is NULL"); + AssertFatal(index < elementCount(), avar("Vector::operator[%i/%i] - out of bounds array access!", index,elementCount())); return mArray[index]; } -template inline const T& Vector::operator[](U32 index) const +template +inline const T& Vector::operator[](U32 index) const { - AssertFatal(index < mElementCount, avar("Vector::operator[%i/%i] - out of bounds array access!", index, mElementCount)); + AssertFatal(index < elementCount(), avar("Vector::operator[%i/%i] - out of bounds array access!", index,elementCount())); return mArray[index]; } -template inline void Vector::reserve(U32 size) +template +inline void Vector::reserve(U32 size) { - if (size <= mArraySize) - return; + if constexpr (COUNT == 0) + { + if (size <= arraySize()) + return; - const U32 ec = mElementCount; - if (resize(size)) - mElementCount = ec; + const U32 ec = elementCount(); + if (resize(size)) + setElementCount(ec); + } } -template inline U32 Vector::capacity() const +template +inline U32 Vector::capacity() const { - return mArraySize; + if constexpr (COUNT == 0) + return arraySize(); + else + return COUNT; } -template inline void Vector::set(void * addr, U32 sz) +template +inline void Vector::set(void* addr, U32 sz) { - if ( !addr ) + if (!addr) sz = 0; - setSize( sz ); + setSize(sz); - if ( addr && sz > 0 ) - dMemcpy(address(),addr,sz*sizeof(T)); + if (addr && sz > 0) + dMemcpy(address(), addr, sz * sizeof(T)); } //----------------------------------------------------------------------------- -template inline bool Vector::resize(U32 ecount) +template +inline bool Vector::resize(U32 ecount) { + bool ret = false; + U32 tArraySize = arraySize(); + U32 tElementCount = elementCount(); #ifdef TORQUE_DEBUG_GUARD - return VectorResize(&mArraySize, &mElementCount, (void**) &mArray, ecount, sizeof(T), - mFileAssociation, mLineAssociation); + ret = VectorResize(&tArraySize, &tElementCount, (void**)&mArray, ecount, sizeof(T), + mFileAssociation, mLineAssociation); #else - return VectorResize(&mArraySize, &mElementCount, (void**) &mArray, ecount, sizeof(T)); + ret = VectorResize(&tArraySize, &tElementCount, (void**)&mArray, ecount, sizeof(T)); #endif + setArraySize(tArraySize); + setElementCount(tElementCount); + // No construct/destroy here for COUNT == 0 + // For fixed-size vectors, we may still want to backfill + if constexpr (COUNT > 0) { + AssertFatal(tElementCount <= tArraySize, "Vector::resize - elementCount exceeds arraySize after resize"); + AssertFatal(mArray != NULL || ecount == 0, "Vector::resize - mArray should be valid or NULL if ecount == 0"); + for (U32 i = ecount; i < arraySize(); ++i) + constructInPlace(&mArray[i]); + } + else + { + if (ecount == 0) + mArray = NULL; + } + return ret; } -template inline void Vector::merge( const Vector &p ) +template +inline void Vector::merge(const Vector& p) { - if ( !p.size() ) + if (!p.size()) return; - - const U32 oldSize = mElementCount; + const U32 oldSize = elementCount(); const U32 newSize = oldSize + p.size(); - if ( newSize > mArraySize ) - resize( newSize ); - - T *dest = mArray + oldSize; - const T *src = p.mArray; - while ( dest < mArray + newSize ) - constructInPlace( dest++, src++ ); - - mElementCount = newSize; + if (newSize > arraySize()) + resize(newSize); + T* dest = mArray + oldSize; + const T* src = p.mArray; + while (dest < mArray + newSize) + constructInPlace(dest++, src++); + setElementCount(newSize); } -template inline void Vector::merge( const T *addr, U32 count ) +template +inline void Vector::merge(const T* addr, U32 count) { - const U32 oldSize = mElementCount; + const U32 oldSize = elementCount(); const U32 newSize = oldSize + count; - if ( newSize > mArraySize ) - resize( newSize ); - - T *dest = mArray + oldSize; - while ( dest < mArray + newSize ) - constructInPlace( dest++, addr++ ); - - mElementCount = newSize; + if (newSize > arraySize()) + resize(newSize); + T* dest = mArray + oldSize; + while (dest < mArray + newSize) + constructInPlace(dest++, addr++); + setElementCount(newSize); } -template inline void Vector::reverse() +template +inline void Vector::reverse() { - for (U32 i = 0, j = size(); (i != j) && (i != --j); ++i) - std::swap( mArray[ i ], mArray[ j ] ); + // Reverses the order of elements in the vector. + for (U32 i = 0, j = size(); (i != j) && (i != --j); ++i) + std::swap(mArray[i], mArray[j]); +} + +template +inline void Vector::insert(const T* src, U32 count, U32 idx) +{ + // Inserts a block of elements at index. + if (!src || count == 0) + return; + if constexpr (COUNT == 0) + { + auto* self = static_cast*>(this); + AssertFatal(idx <= self->elementCount(), "Vector::insert - out of bounds index."); + self->setSize(self->elementCount() + count); + U32 moveCount = self->elementCount() - idx - count; + if (moveCount > 0) + dMemmove(&self->mArray[idx + count], &self->mArray[idx], moveCount * sizeof(T)); + for (U32 i = 0; i < count; ++i) + self->mArray[idx + i] = src[i]; + } + else + { + AssertFatal(idx < COUNT, "Vector::insert - out of bounds index."); + U32 moveCount = COUNT - idx - count; + if (moveCount > 0) + dMemmove(&mArray[idx + count], &mArray[idx], moveCount * sizeof(T)); + for (U32 i = 0; i < count; ++i) + mArray[idx + i] = src[i]; + } +} + +template +inline void Vector::fill(const T& value, U32 count) +{ + // Fills first 'count' elements with value. + U32 n = (count > size()) ? size() : count; + for (U32 i = 0; i < n; ++i) + mArray[i] = value; +} + +template +inline void Vector::fill(const T& value, U32 count, U32 offset) +{ + // Fills 'count' elements starting at 'offset' with value. + U32 start = offset; + U32 n = (count == (U32)-1) ? (this->size() - start) : count; + U32 limit = this->size(); + if (start >= limit) + return; + if (start + n > limit) + n = limit - start; + for (U32 i = 0; i < n; ++i) + mArray[start + i] = value; +} + + +template +inline void Vector::copy(const T* src) +{ + // Copies data from an external array to the vector. + if (!src || size() == 0) + return; + for (U32 i = 0; i < size(); ++i) + mArray[i] = src[i]; +} + +template +template +inline void Vector::moveFrom(SrcVec& src) +{ + // Moves data from another vector, leaving it in a valid but unspecified state. + U32 n = mMin(COUNT, src.size()); + for (U32 i = 0; i < n; ++i) + mArray[i] = std::move(src.mArray[i]); + src.clear(); +} + + +//// review these! + +template +template +inline U32 Vector::countIf(Func fn) const +{ + // Counts elements matching a predicate. + U32 n = 0; + for (U32 i = 0; i < size(); ++i) + if (fn(mArray[i])) + ++n; + return n; +} + +template +template +inline bool Vector::anyOf(Func fn) const +{ + // Returns true if any element matches the predicate. + for (U32 i = 0; i < size(); ++i) + if (fn(mArray[i])) + return true; +} + +template +template +inline bool Vector::allOf(Func fn) const +{ + // Returns true if all elements match the predicate. + for (U32 i = 0; i < size(); ++i) + if (!fn(mArray[i])) + return false; + return true; +} + +// brief Assigns elements from another vector to this vector. +// // Copies up to n elements from the source vector (starting at srcOffset) to this vector (starting at targOffset). +// Handles overlapping ranges safely and resizes this vector if needed (for dynamic vectors). +// For fixed-size vectors, only copies up to the available capacity. +template +template +inline void Vector::assign(const SrcVec* src, U32 n, U32 targOffset, U32 srcOffset) +{ + U32 targLen = this->size(); + U32 srcLen = src->size(); + if (n == 0 || targOffset + n > targLen || srcOffset + n > srcLen) + n = mMin(targLen - targOffset, srcLen - srcOffset); + if constexpr (COUNT == 0) + if (targOffset + n > this->elementCount()) + this->setSize(targOffset + n); + if (n == 0) + return; + for (U32 i = 0; i < n; ++i) + this->mArray[targOffset + i] = src->mArray[srcOffset + i]; +} + + + + +template +inline void Vector::reverse(U32 offset, U32 count) +{ + // Reverses the order of a range of elements. + U32 limit = this->size(); + if (offset >= limit || count < 2) + return; + if (offset + count > limit) + count = limit - offset; + VectorHelpers::reverse(this, count, offset); +} + +template +template +void Vector::swap(Vector& other) +{ + // Swaps elements with another vector. + U32 n = mMin(this->size(), other.size()); + for (U32 i = 0; i < n; ++i) + std::swap(this->mArray[i], other.mArray[i]); +} + +template +template +void Vector::swap(Vector& other, U32 thisOffset, U32 otherOffset, U32 count) +{ + // Swaps a range of elements with another vector. + U32 thisLimit = this->size(); + U32 otherLimit = other.size(); + U32 n = count; + if (thisOffset >= thisLimit || otherOffset >= otherLimit) + return; + if (thisOffset + n > thisLimit) + n = thisLimit - thisOffset; + if (otherOffset + n > otherLimit) + n = otherLimit - otherOffset; + for (U32 i = 0; i < n; ++i) + std::swap(this->mArray[thisOffset + i], other.mArray[otherOffset + i]); +} + +template +template +inline void Vector::forEach(Func fn) +{ + // Applies a function to each element in the vector. + for (U32 i = 0; i < size(); ++i) + fn(mArray[i]); +} + +template +template +inline void Vector::removeIf(Func fn) +{ + // Removes elements matching a predicate. + U32 write = 0; + for (U32 read = 0; read < elementCount(); ++read) + { + if (!fn(mArray[read])) + { + if (write != read) + mArray[write] = std::move(mArray[read]); + ++write; + } + else + destructInPlace(&mArray[read]); + } + setElementCount(write); +} + +template +template +inline bool Vector::compare(const Vector& other) const +{ + U32 n = mMin(this->size(), other.size()); + return VectorHelpers::compare(this, &other, n, 0, 0); +} + +template +template +inline bool Vector::compare(const Vector& other, U32 thisOffset, U32 otherOffset, U32 count) const +{ + U32 thisLimit = this->size(); + U32 otherLimit = other.size(); + U32 n = count; + if (thisOffset >= thisLimit || otherOffset >= otherLimit) + return false; + if (thisOffset + n > thisLimit) + n = thisLimit - thisOffset; + if (otherOffset + n > otherLimit) + n = otherLimit - otherOffset; + return VectorHelpers::compare(this, &other, n, thisOffset, otherOffset); +} + +template +inline Vector::Vector(const U32 initialSize, const char* fileName, const U32 lineNum) +{ + zeroState(); +#ifdef TORQUE_DEBUG_GUARD + mFileAssociation = fileName; + mLineAssociation = lineNum; +#else + // TORQUE_UNUSED(fileName); + // TORQUE_UNUSED(lineNum); +#endif + if constexpr (COUNT == 0) + { + if (initialSize) + reserve(initialSize); + } } //----------------------------------------------------------------------------- /// Template for vectors of pointers. template -class VectorPtr : public Vector +class VectorPtr : public Vector { /// @deprecated Disallowed. VectorPtr(const VectorPtr&); // Disallowed - public: +public: VectorPtr(); VectorPtr(const char* fileName, const U32 lineNum); @@ -812,10 +1956,9 @@ class VectorPtr : public Vector /// @{ typedef T value_type; - typedef T& reference; + typedef T& reference; typedef const T& const_reference; - - typedef T* iterator; + typedef T* iterator; typedef const T* const_iterator; typedef U32 difference_type; typedef U32 size_type; @@ -825,18 +1968,18 @@ class VectorPtr : public Vector iterator end(); const_iterator end() const; - void insert(iterator,const T&); + void insert(iterator, const T&); void insert(S32 idx) { Parent::insert(idx); } void erase(iterator); - T& front(); + T& front(); const T& front() const; - T& back(); + T& back(); const T& back() const; void push_front(const T&); void push_back(const T&); - T& operator[](U32); + T& operator[](U32); const T& operator[](U32) const; /// @} @@ -844,9 +1987,9 @@ class VectorPtr : public Vector /// @name Extended interface /// @{ - typedef Vector Parent; - T& first(); - T& last(); + typedef Vector Parent; + T& first(); + T& last(); const T& first() const; const T& last() const; void erase_fast(U32); @@ -857,144 +2000,145 @@ class VectorPtr : public Vector //----------------------------------------------------------------------------- -template inline VectorPtr::VectorPtr() +template inline VectorPtr::VectorPtr() { // } -template inline VectorPtr::VectorPtr(const char* fileName, - const U32 lineNum) +template inline VectorPtr::VectorPtr(const char* fileName, + const U32 lineNum) : Vector(fileName, lineNum) { // } -template inline T& VectorPtr::first() +template inline T& VectorPtr::first() { return (T&)Parent::first(); } -template inline const T& VectorPtr::first() const +template inline const T& VectorPtr::first() const { return (const T)Parent::first(); } -template inline T& VectorPtr::last() +template inline T& VectorPtr::last() { return (T&)Parent::last(); } -template inline const T& VectorPtr::last() const +template inline const T& VectorPtr::last() const { return (const T&)Parent::last(); } -template inline typename VectorPtr::iterator VectorPtr::begin() +template inline typename VectorPtr::iterator VectorPtr::begin() { return (iterator)Parent::begin(); } -template inline typename VectorPtr::const_iterator VectorPtr::begin() const +template inline typename VectorPtr::const_iterator VectorPtr::begin() const { return (const_iterator)Parent::begin(); } -template inline typename VectorPtr::iterator VectorPtr::end() +template inline typename VectorPtr::iterator VectorPtr::end() { return (iterator)Parent::end(); } -template inline typename VectorPtr::const_iterator VectorPtr::end() const +template inline typename VectorPtr::const_iterator VectorPtr::end() const { return (const_iterator)Parent::end(); } -template inline void VectorPtr::insert(iterator i,const T& x) +template inline void VectorPtr::insert(iterator i, const T& x) { - Parent::insert( (Parent::iterator)i, (Parent::reference)x ); + Parent::insert((Parent::iterator)i, (Parent::reference)x); } -template inline void VectorPtr::erase(iterator i) +template inline void VectorPtr::erase(iterator i) { - Parent::erase( (Parent::iterator)i ); + Parent::erase((Parent::iterator)i); } -template inline void VectorPtr::erase_fast(U32 index) +template inline void VectorPtr::erase_fast(U32 index) { - AssertFatal(index < mElementCount, "VectorPtr::erase_fast - out of bounds index." ); + AssertFatal(index < elementCount(), "VectorPtr::erase_fast - out of bounds index."); // CAUTION: this operator does not maintain list order // Copy the last element into the deleted 'hole' and decrement the // size of the vector. - // Assert: index >= 0 && index < mElementCount - if (index < (mElementCount - 1)) - mArray[index] = mArray[mElementCount - 1]; + // Assert: index >= 0 && index < elementCount() + if (index < (elementCount() - 1)) + mArray[index] = mArray[elementCount() - 1]; decrement(); } -template inline void VectorPtr::erase_fast(iterator i) +template inline void VectorPtr::erase_fast(iterator i) { erase_fast(U32(i - iterator(mArray))); } -template inline T& VectorPtr::front() +template inline T& VectorPtr::front() { return *begin(); } -template inline const T& VectorPtr::front() const +template inline const T& VectorPtr::front() const { return *begin(); } -template inline T& VectorPtr::back() +template inline T& VectorPtr::back() { - AssertFatal(mElementCount != 0, "Vector::back - cannot access last element of zero-length vector."); - return *(end()-1); + AssertFatal(elementCount(), "Vector::back - cannot access last element of zero-length vector."); + return *(end() - 1); } -template inline const T& VectorPtr::back() const +template inline const T& VectorPtr::back() const { - AssertFatal(mElementCount != 0, "Vector::back - cannot access last element of zero-length vector."); - return *(end()-1); + AssertFatal(elementCount(), "Vector::back - cannot access last element of zero-length vector."); + return *(end() - 1); } -template inline void VectorPtr::push_front(const T& x) +template inline void VectorPtr::push_front(const T& x) { Parent::push_front((Parent::const_reference)x); } -template inline void VectorPtr::push_back(const T& x) +template inline void VectorPtr::push_back(const T& x) { Parent::push_back((Parent::const_reference)x); } -template inline T& VectorPtr::operator[](U32 index) +template inline T& VectorPtr::operator[](U32 index) { return (T&)Parent::operator[](index); } -template inline const T& VectorPtr::operator[](U32 index) const +template inline const T& VectorPtr::operator[](U32 index) const { return (const T&)Parent::operator[](index); } - //------------------------------------------------------------------------------ - +template struct CustomAllocator; +template class Vector; template class VectorSet : public Vector { public: void insert(T dat) { - if(find(this->begin(), this->end(), dat) == this->end()) + if (VectorHelpers::find(this, -1, dat) == -1) push_back(dat); } }; + + // Include vector specializations #ifndef _TVECTORSPEC_H_ #include "core/util/tVectorSpecializations.h" #endif #endif //_TVECTOR_H_ - diff --git a/Engine/source/core/util/tVectorSpecializations.h b/Engine/source/core/util/tVectorSpecializations.h index 8f9970fc6..f1bb179c3 100644 --- a/Engine/source/core/util/tVectorSpecializations.h +++ b/Engine/source/core/util/tVectorSpecializations.h @@ -27,6 +27,8 @@ #include "core/util/tVector.h" #endif +template struct CustomAllocator; +template class Vector; // Not exactly a specialization, just a vector to use when speed is a concern template class FastVector : public Vector diff --git a/Engine/source/lighting/lightingInterfaces.h b/Engine/source/lighting/lightingInterfaces.h index dc4db77a4..fead951eb 100644 --- a/Engine/source/lighting/lightingInterfaces.h +++ b/Engine/source/lighting/lightingInterfaces.h @@ -34,7 +34,8 @@ class ObjectProxy; class ObjectProxyList; class SceneLightingInterface; -template class Vector; +template struct CustomAllocator; +template class Vector; typedef Vector SceneLightingInterfaces; // List of available "systems" that the lighting kit can use diff --git a/Engine/source/platform/platform.h b/Engine/source/platform/platform.h index acb929a9f..f7c9c04fd 100644 --- a/Engine/source/platform/platform.h +++ b/Engine/source/platform/platform.h @@ -110,7 +110,7 @@ enum DriveType // Some forward declares for later. class Point2I; -template class Vector; + template class Signal; struct InputEventInfo; @@ -243,10 +243,10 @@ namespace Platform bool isFullPath(const char *path); StringTableEntry makeRelativePathName(const char *path, const char *to); - String stripExtension( String fileName, Vector< String >& validExtensions ); + String stripExtension( String fileName, Vector >& validExtensions ); - bool dumpPath(const char *in_pBasePath, Vector& out_rFileVector, S32 recurseDepth = -1); - bool dumpDirectories( const char *path, Vector &directoryVector, S32 depth = 0, bool noBasePath = false ); + bool dumpPath(const char *in_pBasePath, Vector >& out_rFileVector, S32 recurseDepth = -1); + bool dumpDirectories( const char *path, Vector >&directoryVector, S32 depth = 0, bool noBasePath = false ); bool hasSubDirectory( const char *pPath ); bool getFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime); bool isFile(const char *pFilePath); @@ -283,8 +283,8 @@ namespace Platform extern struct VolumeInformation *PVolumeInformation; // Volume functions. - void getVolumeNamesList( Vector& out_rNameVector, bool bOnlyFixedDrives = false ); - void getVolumeInformationList( Vector& out_rVolumeInfoVector, bool bOnlyFixedDrives = false ); + void getVolumeNamesList(Vector >& out_rNameVector, bool bOnlyFixedDrives = false ); + void getVolumeInformationList(Vector >& out_rVolumeInfoVector, bool bOnlyFixedDrives = false ); struct SystemInfo_struct { diff --git a/Engine/source/platform/platformFont.h b/Engine/source/platform/platformFont.h index 63a47d95a..ebc7a229a 100644 --- a/Engine/source/platform/platformFont.h +++ b/Engine/source/platform/platformFont.h @@ -89,7 +89,7 @@ public: /// /// @todo Rethink this so we don't have a private public. virtual bool create( const char *name, dsize_t size, U32 charset = TGE_ANSI_CHARSET ) = 0; - static void enumeratePlatformFonts( Vector& fonts, UTF16* fontFamily = NULL ); + static void enumeratePlatformFonts( Vector>& fonts, UTF16* fontFamily = NULL ); }; extern PlatformFont *createPlatformFont(const char *name, dsize_t size, U32 charset = TGE_ANSI_CHARSET); diff --git a/Engine/source/scene/sceneContainer.cpp b/Engine/source/scene/sceneContainer.cpp index 81a437392..1503befac 100644 --- a/Engine/source/scene/sceneContainer.cpp +++ b/Engine/source/scene/sceneContainer.cpp @@ -443,7 +443,7 @@ bool SceneContainer::removeObject(SceneObject* obj) if ( obj->getTypeMask() & ( WaterObjectType | PhysicalZoneObjectType ) ) { iter = std::find( mWaterAndZones.begin(), mWaterAndZones.end(), obj ); - if( iter != mTerrains.end() ) + if( iter != mWaterAndZones.end() ) mWaterAndZones.erase_fast(iter); } diff --git a/Engine/source/scene/sceneObject.cpp b/Engine/source/scene/sceneObject.cpp index b73326ae5..3ce8449e3 100644 --- a/Engine/source/scene/sceneObject.cpp +++ b/Engine/source/scene/sceneObject.cpp @@ -336,13 +336,14 @@ void SceneObject::onRemove() { smSceneObjectRemove.trigger(this); + if (getParent() != NULL) attachToParent(NULL); unmount(); plUnlink(); + if (mContainer) + mContainer->removeObject(this); + Parent::onRemove(); -// PATHSHAPE - if ( getParent() != NULL) attachToParent( NULL); -// PATHSHAPE END } //----------------------------------------------------------------------------- @@ -395,7 +396,7 @@ void SceneObject::inspectPostApply() //----------------------------------------------------------------------------- void SceneObject::setGlobalBounds() -{ +{ mGlobalBounds = true; mObjBox.minExtents.set( -1e10, -1e10, -1e10 ); mObjBox.maxExtents.set( 1e10, 1e10, 1e10 ); @@ -434,6 +435,7 @@ void SceneObject::setTransform( const MatrixF& mat ) mSceneManager->notifyObjectDirty( this ); setRenderTransform( mat ); + setMountTransforms(); } //----------------------------------------------------------------------------- @@ -1351,6 +1353,19 @@ void SceneObject::getRenderMountTransform( F32 delta, S32 index, const MatrixF & outMat->mul( mRenderObjToWorld, mountTransform ); } +void SceneObject::setMountTransforms() +{ + // Update all objects mounted to us + for (SceneObject* mounted = mMount.list; mounted; mounted = mounted->mMount.link) + { + // Compute mounted object's new world transform + MatrixF worldXfm; + getMountTransform(mounted->mMount.node, mounted->mMount.xfm, &worldXfm); + mounted->setTransform(worldXfm); + } +} + + //============================================================================= // Console API. //============================================================================= @@ -1666,7 +1681,8 @@ void SceneObject::moveRender(const Point3F &delta) void SceneObject::PerformUpdatesForChildren(MatrixF mat){ for (U32 i=0; i < getNumChildren(); i++) { SceneObject *o = getChild(i); - o->updateChildTransform(); //update the position of the child object + if (!o->isMounted()) + o->updateChildTransform(); //update the position of the child object } } diff --git a/Engine/source/scene/sceneObject.h b/Engine/source/scene/sceneObject.h index e12f1a0f8..9223c674c 100644 --- a/Engine/source/scene/sceneObject.h +++ b/Engine/source/scene/sceneObject.h @@ -674,6 +674,7 @@ class SceneObject : public NetObject, public ProcessObject void resolveMountPID(); + void setMountTransforms(); /// @} /// @name Sound From 6fcb08bb8652e019342462f6d585a0f2c38440d3 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Wed, 13 May 2026 14:53:39 -0500 Subject: [PATCH 12/17] use t3d methods, not raw std. shadowvar fix. --- Engine/source/core/util/tVector.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Engine/source/core/util/tVector.h b/Engine/source/core/util/tVector.h index 79ade2fe4..8c54d5d1e 100644 --- a/Engine/source/core/util/tVector.h +++ b/Engine/source/core/util/tVector.h @@ -94,7 +94,7 @@ struct VectorTraits; template struct VectorTraits> { - static constexpr U32 COUNT = COUNT; + static constexpr U32 mTCount = COUNT; using allocator = Allocator; using value_type = T; }; @@ -203,7 +203,7 @@ namespace VectorHelpers U32 vecCapacity = vec.capacity(); if (!buffer || n == 0 || offset >= vecCapacity) return; if (offset + n > vecCapacity) n = vecCapacity - offset; - if (!toBuffer && U32(0) == 0 && offset + n > vec.size()) vec.setSize(offset + n); + if (offset + n > vec.size()) vec.setSize(offset + n); T* vecArray = vec.array(); if (!moveElements) { if (toBuffer) toBuffer(&vec, buffer, n, offset); @@ -561,7 +561,7 @@ namespace VectorHelpers U32 len = n ? n : targLen - targOffset; if (len == 0) return; if constexpr (std::is_trivially_copyable::value) - std::memmove(targ->array() + targOffset, src + srcOffset, len * sizeof(T)); + dMemmove(targ->array() + targOffset, src + srcOffset, len * sizeof(T)); else for (U32 i = 0; i < len; ++i) targ->array()[targOffset + i] = src[srcOffset + i]; @@ -578,7 +578,7 @@ namespace VectorHelpers U32 len = n ? n : targLen - targOffset; if (len == 0) return; if constexpr (std::is_trivially_copyable::value) - std::memmove(targ->array() + targOffset, src->array() + srcOffset, len * sizeof(T)); + dMemmove(targ->array() + targOffset, src->array() + srcOffset, len * sizeof(T)); else if constexpr (std::is_move_assignable::value && std::is_move_constructible::value) for (U32 i = 0; i < len; ++i) { targ->array()[targOffset + i] = std::move(src->array()[srcOffset + i]); @@ -612,7 +612,7 @@ namespace VectorHelpers if constexpr (std::is_trivially_copyable::value) { if constexpr (std::is_integral::value || std::is_floating_point::value || std::is_pointer::value) { if (val == T()) { - std::memset(vec->array() + offset, 0, n * sizeof(T)); + dMemset(vec->array() + offset, 0, n * sizeof(T)); return; } } @@ -640,7 +640,7 @@ namespace VectorHelpers targ->setSize(targOffset + n); if (n == 0) return; if constexpr (isPod) - std::memmove(targ->array() + targOffset, src->array() + srcOffset, n * sizeof(T)); + dMemmove(targ->array() + targOffset, src->array() + srcOffset, n * sizeof(T)); else if constexpr (std::is_copy_assignable::value) { if (targ == src && std::abs((int)targOffset - (int)srcOffset) < (int)n) { std::unique_ptr temp(new T[n]); @@ -755,7 +755,7 @@ namespace VectorHelpers if (!vec || !src || n == 0) return; T* arr = vec->array(); if constexpr (std::is_trivially_copyable::value) { - std::memmove(arr + destOffset, src, n * sizeof(T)); + dMemmove(arr + destOffset, src, n * sizeof(T)); } else if (arr + destOffset > src && arr + destOffset < src + n) { for (S32 i = n - 1; i >= 0; --i) @@ -1005,7 +1005,7 @@ public: static EngineFieldTable::Field getElementCountField() { typedef Vector ThisType; - if constexpr (VectorTraits::COUNT == 0) + if constexpr (VectorTraits::mTCount == 0) { return EngineFieldTable::Field{ "elementCount", "", 1, @@ -1021,7 +1021,7 @@ public: { typedef Vector ThisType; - if constexpr (VectorTraits::COUNT == 0) + if constexpr (VectorTraits::mTCount == 0) { return EngineFieldTable::Field{ "arraySize", "", 1, @@ -1763,7 +1763,7 @@ template inline void Vector::moveFrom(SrcVec& src) { // Moves data from another vector, leaving it in a valid but unspecified state. - U32 n = mMin(COUNT, src.size()); + U32 n = mMin(COUNT > 0 ? COUNT : this->arraySize(), src.size()); for (U32 i = 0; i < n; ++i) mArray[i] = std::move(src.mArray[i]); src.clear(); From fb75d0fefb983baf8498429d77184a9b88c41244 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Wed, 13 May 2026 15:33:59 -0500 Subject: [PATCH 13/17] mac was somehow not already including "math/mMathFn.h" in the chain --- Engine/source/core/util/tVector.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Engine/source/core/util/tVector.h b/Engine/source/core/util/tVector.h index 8c54d5d1e..b14388cd9 100644 --- a/Engine/source/core/util/tVector.h +++ b/Engine/source/core/util/tVector.h @@ -33,7 +33,9 @@ #include #include "console/engineTypes.h" #include "console/engineTypeInfo.h" - +#ifndef _MMATHFN_H_ +#include "math/mMathFn.h" +#endif //----------------------------------------------------------------------------- // Helper definitions for the vector class. From 39fbd48d5a1a942e9d40472a88bc5f234fb6313d Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Wed, 13 May 2026 16:33:23 -0500 Subject: [PATCH 14/17] be more precice when it comes to templates on unix derivatives --- Engine/source/core/util/treeObject.h | 112 +++++++++++++-------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index e2e77c220..183144fd7 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -67,7 +67,7 @@ public: // Creates a deep copy of this node and its entire subtree. inline TreeNode* clone() const { auto* copy = new TreeNode(data, NULL); - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) copy->addChild((*this)[i]->clone()); return copy; @@ -77,7 +77,7 @@ public: inline TreeNode* cloneIf(Func pred) const { if (!pred(this)) return NULL; auto* newNode = new TreeNode(data, NULL); - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) { auto* filteredChild = (*this)[i]->cloneIf(pred); if (filteredChild) @@ -87,9 +87,9 @@ public: } inline bool equals(const TreeNode* other) const { - if (!other || data != other->data || size() != other->size()) + if (!other || data != other->data || this->size() != other->size()) return false; - for (U32 i = 0; i < size(); ++i) { + for (U32 i = 0; i < this->size(); ++i) { if ((*this)[i] && other->getChild(i)) { if (!(*this)[i]->equals(other->getChild(i))) return false; @@ -141,9 +141,9 @@ public: inline bool isRoot() const { return parent == NULL; } inline bool isLeaf() const { if constexpr (nullClears) - return size() == 0; + return this->size() == 0; else { - for (U32 i = 0; i < size(); ++i) { + for (U32 i = 0; i < this->size(); ++i) { if ((*this)[i] != NULL) return false; } @@ -152,39 +152,39 @@ public: } inline bool hasChildren() const { if constexpr (nullClears) - return size() > 0; + return this->size() > 0; else { - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) return true; return false; } } inline U32 getNumChildren() const { if constexpr (nullClears) - return size(); + return this->size(); else { U32 count = 0; - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) ++count; return count; } } inline bool hasChild(TreeNode* child) const { if (!child) return false; - for (U32 i = 0; i < size(); i++) + for (U32 i = 0; i < this->size(); i++) if ((*this)[i] == child) return true; return false; } inline S32 getChildIndex(TreeNode* child) const { if (!child) return -1; - for (U32 i = 0; i < size(); i++) + for (U32 i = 0; i < this->size(); i++) if ((*this)[i] == child) return (S32)i; return -1; } inline bool isValidTree() const { - for (U32 i = 0; i < size(); i++) { + for (U32 i = 0; i < this->size(); i++) { TreeNode* child = (*this)[i]; if (child && child->parent != this) return false; @@ -200,7 +200,7 @@ public: template inline TreeNode* findLeafIf(Func pred) { if (isLeaf() && pred(this)) return this; - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) { auto* found = (*this)[i]->findLeafIf(pred); if (found) return found; @@ -329,7 +329,7 @@ public: return child; } inline void addChild(TreeNode* child) { - for (U32 i = 0; i < size(); i++) + for (U32 i = 0; i < this->size(); i++) AssertFatal((*this)[i] != child, "TreeNode::addChild - Attempted to add duplicate child"); #ifdef TORQUE_DEBUG TreeNode* ancestor = this; @@ -345,10 +345,10 @@ public: push_back_unique(child); } inline void setChild(U32 i, TreeNode* child) { - if (i >= size()) { - U32 oldSize = size(); + if (i >= this->size()) { + U32 oldSize = this->size(); increment((i + 1) - oldSize); - for (U32 j = oldSize; j < size(); j++) + for (U32 j = oldSize; j < this->size(); j++) (*this)[j] = NULL; } TreeNode* oldChild = (*this)[i]; @@ -357,7 +357,7 @@ public: oldChild->parent = NULL; if (child == NULL) { if constexpr (nullClears) - erase(i); + this->erase(i); else (*this)[i] = NULL; return; @@ -373,21 +373,21 @@ public: child->parent->removeChild(child); } else if (child->parent == this) { - for (U32 j = 0; j < size(); j++) { + for (U32 j = 0; j < this->size(); j++) { if (j != i && (*this)[j] == child) if constexpr (nullClears) - erase(j); + this->erase(j); else (*this)[j] = NULL; } } - for (U32 j = 0; j < size(); j++) + for (U32 j = 0; j < this->size(); j++) AssertFatal(j == i || (*this)[j] != child, "TreeNode::setChild - Attempted to set duplicate child"); (*this)[i] = child; child->parent = this; } inline TreeNode* getChild(U32 i) { - if (i < size()) { + if (i < this->size()) { if ((*this)[i] && (*this)[i]->parent != this) { AssertFatal(false, "TreeNode::getChild - Child parent pointer mismatch"); } @@ -396,7 +396,7 @@ public: return NULL; } inline const TreeNode* getChild(U32 i) const { - if (i < size()) { + if (i < this->size()) { if ((*this)[i] && (*this)[i]->parent != this) { AssertFatal(false, "TreeNode::getChild - Child parent pointer mismatch"); } @@ -407,7 +407,7 @@ public: inline Vector*> getChildren() const { Vector*> children; - for (U32 i = 0; i < size(); ++i) { + for (U32 i = 0; i < this->size(); ++i) { if ((*this)[i]) children.push_back((*this)[i]); } @@ -421,7 +421,7 @@ public: if (!child) continue; bool isDuplicate = false; - for (U32 j = 0; j < size(); j++) { + for (U32 j = 0; j < this->size(); j++) { if ((*this)[j] == child) { isDuplicate = true; break; @@ -456,7 +456,7 @@ public: } } inline void clearChildren() { - for (U32 i = 0; i < size(); i++) { + for (U32 i = 0; i < this->size(); i++) { TreeNode* child = (*this)[i]; if (child) { if (child->parent != this) { @@ -470,11 +470,11 @@ public: } } } - clear(); + this->clear(); } inline void deleteChildren() { Vector*> children; - for (U32 i = 0; i < size(); i++) { + for (U32 i = 0; i < this->size(); i++) { TreeNode* child = (*this)[i]; if (child) { if (child->parent != this) { @@ -489,7 +489,7 @@ public: } children.push_back_unique(child); } - clear(); + this->clear(); for (U32 i = 0; i < children.size(); i++) { if (children[i]) { delete children[i]; @@ -499,11 +499,11 @@ public: inline void removeChild(TreeNode* child) { AssertFatal(child != NULL, "TreeNode::removeChild - Attempted to remove NULL child"); bool found = false; - for (U32 i = 0; i < size(); i++) { + for (U32 i = 0; i < this->size(); i++) { if ((*this)[i] == child) { child->parent = NULL; if constexpr (nullClears) - erase(i); + this->erase(i); else (*this)[i] = NULL; found = true; @@ -512,11 +512,11 @@ public: } if (!found) { #ifdef TORQUE_DEBUG - for (U32 i = 0; i < size(); i++) { + for (U32 i = 0; i < this->size(); i++) { if ((*this)[i] && (*this)[i]->parent == this) { (*this)[i]->parent = NULL; if constexpr (nullClears) - erase(i); + this->erase(i); else (*this)[i] = NULL; Con::warnf("TreeNode::removeChild - Repaired mismatched child pointer at index %u", i); @@ -531,12 +531,12 @@ public: AssertFatal(found, "TreeNode::removeChild - Child not found in parent's children array"); } inline void removeChild(U32 i) { - AssertFatal(i < size(), "TreeNode::removeChild - Index out of bounds"); + AssertFatal(i < this->size(), "TreeNode::removeChild - Index out of bounds"); TreeNode* child = (*this)[i]; if (child) child->parent = NULL; if constexpr (nullClears) - erase(i); + this->erase(i); else (*this)[i] = NULL; } @@ -547,7 +547,7 @@ public: setChild((U32)idx, newChild); } inline void nullChild(U32 i) { - if (i >= size()) + if (i >= this->size()) return; TreeNode* child = (*this)[i]; if (child && child->parent == this) @@ -555,7 +555,7 @@ public: (*this)[i] = NULL; } inline void swapChildren(U32 i, U32 j) { - if (i >= size() || j >= size() || i == j) + if (i >= this->size() || j >= this->size() || i == j) return; TreeNode* childI = (*this)[i]; TreeNode* childJ = (*this)[j]; @@ -589,33 +589,33 @@ public: if (isLeaf()) out.push_back_unique(this); else - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) (*this)[i]->getLeaves(out); } inline void collectSubtree(Vector*>& nodes) { nodes.push_back_unique(this); - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) (*this)[i]->collectSubtree(nodes); } inline void collectSubtreeData(Vector& dataList) { dataList.push_back_unique(data); - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) (*this)[i]->collectSubtreeData(dataList); } // callback signature: void(TreeNode*) template inline void forEachChild(Func callback) { - for (U32 i = 0; i < size(); i++) + for (U32 i = 0; i < this->size(); i++) if ((*this)[i]) callback((*this)[i]); } // callback signature: void(const TreeNode*) template inline void forEachChild(Func callback) const { - for (U32 i = 0; i < size(); i++) + for (U32 i = 0; i < this->size(); i++) if ((*this)[i]) callback((*this)[i]); } @@ -623,7 +623,7 @@ public: template inline void forEachInSubtree(Func callback) { callback(this); - for (U32 i = 0; i < size(); i++) + for (U32 i = 0; i < this->size(); i++) if ((*this)[i]) (*this)[i]->forEachInSubtree(callback); } @@ -633,7 +633,7 @@ public: if (isLeaf()) callback(this); else - for (U32 i = 0; i < size(); i++) + for (U32 i = 0; i < this->size(); i++) if ((*this)[i]) (*this)[i]->forEachLeaf(callback); } @@ -666,7 +666,7 @@ public: template inline TreeNode* findIf(Func pred) { if (pred(this)) return this; - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) { auto* found = (*this)[i]->findIf(pred); if (found) return found; @@ -678,7 +678,7 @@ public: inline void findAllIf(Func pred, Vector*>& out) { if (pred(this)) out.push_back(this); - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) (*this)[i]->findAllIf(pred, out); } @@ -686,7 +686,7 @@ public: template inline U32 countIf(Func pred) const { U32 count = pred(this) ? 1 : 0; - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) count += (*this)[i]->countIf(pred); return count; @@ -694,11 +694,11 @@ public: // callback signature: bool pred(TreeNode*) template inline void removeIf(Func pred) { - for (S32 i = size() - 1; i >= 0; --i) { + for (S32 i = this->size() - 1; i >= 0; --i) { if ((*this)[i] && pred((*this)[i])) { delete (*this)[i]; if constexpr (nullClears) - erase(i); + this->erase(i); else (*this)[i] = NULL; } @@ -712,7 +712,7 @@ public: //------------------------------------------------------------------------- inline U32 getTreeSize() const { U32 count = 1; - for (U32 i = 0; i < size(); i++) + for (U32 i = 0; i < this->size(); i++) if ((*this)[i]) count += (*this)[i]->getTreeSize(); return count; @@ -720,7 +720,7 @@ public: inline U32 getMaxDepth() const { if (isLeaf()) return 0; U32 maxDepth = 0; - for (U32 i = 0; i < size(); i++) + for (U32 i = 0; i < this->size(); i++) if ((*this)[i]) { U32 childDepth = (*this)[i]->getMaxDepth(); if (childDepth > maxDepth) @@ -816,7 +816,7 @@ public: template inline Vector getChildrenAs() { Vector out; - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) out.push_back(static_cast((*this)[i])); return out; @@ -824,7 +824,7 @@ public: template inline Vector getChildrenAs() const { Vector out; - for (U32 i = 0; i < size(); ++i) + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) out.push_back(static_cast((*this)[i])); return out; @@ -834,7 +834,7 @@ public: if (isLeaf()) callback(static_cast(this)); else - for (U32 i = 0; i < size(); i++) + for (U32 i = 0; i < this->size(); i++) if ((*this)[i]) (*this)[i]->template forEachLeafAs(callback); } @@ -843,7 +843,7 @@ public: if (isLeaf()) callback(static_cast(this)); else - for (U32 i = 0; i < size(); i++) + for (U32 i = 0; i < this->size(); i++) if ((*this)[i]) (*this)[i]->template forEachLeafAs(callback); } From c4948c38c9dbc1787c7841b08d4713a0f4e80321 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Wed, 13 May 2026 17:21:30 -0500 Subject: [PATCH 15/17] unit test lambda fixes for older compilers refactor treeobject to better leverage vector COUNT limiter --- Engine/source/core/util/tVector.cpp | 18 +- Engine/source/core/util/treeObject.h | 844 ++++++++++++++++++--------- 2 files changed, 589 insertions(+), 273 deletions(-) diff --git a/Engine/source/core/util/tVector.cpp b/Engine/source/core/util/tVector.cpp index 0993fb08f..ed2b614cd 100644 --- a/Engine/source/core/util/tVector.cpp +++ b/Engine/source/core/util/tVector.cpp @@ -197,9 +197,12 @@ void test_Vector_dynamic() { v.removeIf([](U8 x) { return x == 1; }); AssertFatal(v.size() == 0, "removeIf failed"); v.set(arr, 3); - AssertFatal(v.countIf([](U8 x) { return x > 1; }) == 2, "countIf failed"); - AssertFatal(v.anyOf([](U8 x) { return x == 2; }), "anyOf failed"); - AssertFatal(v.allOf([](U8 x) { return x > 0; }), "allOf failed"); + U32 countifResult = v.countIf([](U8 x) { return x > 2; }); + AssertFatal(countifResult, "countIf failed"); + U8 anyofResult = v.anyOf([](U8 x) { return x == 2; }); + AssertFatal(anyofResult, "anyOf failed"); + U8 allofResult = v.allOf([](U8 x) { return x > 0; }); + AssertFatal(allofResult, "allOf failed"); } void test_Vector_fixed() { @@ -252,9 +255,12 @@ void test_Vector_fixed() { v.removeIf([](U8 x) { return x == 4; }); AssertFatal(v.size() == 10, "removeIf should not change size for fixed vector"); v.copy(arr); - AssertFatal(v.countIf([](U8 x) { return x > 5; }) == 4, "countIf failed"); - AssertFatal(v.anyOf([](U8 x) { return x == 7; }), "anyOf failed"); - AssertFatal(v.allOf([](U8 x) { return x < 10; }), "allOf failed"); + U32 countifResult = v.countIf([](U8 x) { return x > 5; }); + AssertFatal(countifResult == 4, "countIf failed"); + U8 anyofResult = v.anyOf([](U8 x) { return x == 7; }); + AssertFatal(anyofResult, "anyOf failed"); + U8 allofResult = v.allOf([](U8 x) { return x < 10; }); + AssertFatal(allofResult, "allOf failed"); } DefineEngineFunction(test_Vector, void, (), , diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index 183144fd7..73c6e1ec1 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -27,31 +27,138 @@ #include "core/util/tDictionary.h" #include "console/consoleTypes.h" -// Add nullClears as a template argument (default: true. set false for BVH/quad/octrees) -template -class TreeNode : public Vector*> { +// Treenode class for multilateral or fixed (BVH/quad/oct) branching vectors. +// - CHILD_COUNT == 0 => dynamic children list (Vector grows/shrinks normally). +// - CHILD_COUNT != 0 => fixed slot array of children of size CHILD_COUNT. +// In fixed mode we require nullClears == false so empty slots are represented as NULL. +template +class TreeNode : public Vector*, CHILD_COUNT> +{ public: + using Node = TreeNode; + using NodePtr = Node*; + using BaseVec = Vector; + + static constexpr bool mTFixedChildren = (CHILD_COUNT != 0); + + static_assert(!mTFixedChildren && (nullClears == true), + "TreeNode: fixed CHILD_COUNT requires nullClears=false (use NULL slots)."); + //------------------------------------------------------------------------- // Data Members //------------------------------------------------------------------------- T data; - TreeNode* parent; + NodePtr parent; +private: + //------------------------------------------------------------------------- + // Fixed/dynamic child storage helpers + //------------------------------------------------------------------------- + inline void _initChildrenStorage() + { + if constexpr (mTFixedChildren) + { + // Vector default-constructs elements; pointer values are not guaranteed to be nullptr. + // Ensure we start with all empty slots. + for (U32 i = 0; i < CHILD_COUNT; ++i) + (*this)[i] = NULL; + } + } + + inline bool _containsChildPtr(NodePtr child) const + { + if (!child) return false; + for (U32 i = 0; i < this->size(); ++i) + if ((*this)[i] == child) + return true; + return false; + } + + inline bool _insertChildPtrUnique(NodePtr child) + { + if (!child) return false; + + if constexpr (!mTFixedChildren) + { + this->push_back_unique(child); + return true; + } + else + { + // Fixed: place into first NULL slot, do not grow. + for (U32 i = 0; i < CHILD_COUNT; ++i) + { + if ((*this)[i] == child) + return true; // already present + } + + for (U32 i = 0; i < CHILD_COUNT; ++i) + { + if ((*this)[i] == NULL) + { + (*this)[i] = child; + return true; + } + } + + AssertFatal(false, "TreeNode - fixed child capacity exceeded."); + return false; + } + } + + inline void _clearAllChildSlots() + { + if constexpr (!mTFixedChildren) + { + this->clear(); + } + else + { + for (U32 i = 0; i < CHILD_COUNT; ++i) + (*this)[i] = NULL; + } + } + +public: //------------------------------------------------------------------------- // Constructors / Destructor //------------------------------------------------------------------------- - inline TreeNode(const T& val = T(), TreeNode* p = NULL) - : data(val), parent(p) {} + inline TreeNode(const T& val = T(), NodePtr p = NULL) + : data(val), parent(p) + { + _initChildrenStorage(); + } - inline TreeNode(const Vector*>& children, TreeNode* p = NULL) + template + inline TreeNode(const Vector& children, NodePtr p = NULL) : data(T()), parent(p) { - increment(children.size()); - for (U32 i = 0; i < children.size(); ++i) { - (*this)[i] = children[i]; - if (children[i]) { - AssertFatal(children[i] != this, "TreeNode::Constructor - Circular parent relationship"); - children[i]->parent = this; + _initChildrenStorage(); + + if constexpr (!mTFixedChildren) + { + this->increment(children.size()); + for (U32 i = 0; i < children.size(); ++i) + { + (*this)[i] = children[i]; + if (children[i]) + { + AssertFatal(children[i] != this, "TreeNode::Constructor - Circular parent relationship"); + children[i]->parent = this; + } + } + } + else + { + const U32 n = getMin(children.size(), CHILD_COUNT); + for (U32 i = 0; i < n; ++i) + { + (*this)[i] = children[i]; + if (children[i]) + { + AssertFatal(children[i] != this, "TreeNode::Constructor - Circular parent relationship"); + children[i]->parent = this; + } } } } @@ -61,12 +168,13 @@ public: deleteChildren(); parent = NULL; } + //------------------------------------------------------------------------- // Deep Copy / Cloning //------------------------------------------------------------------------- - // Creates a deep copy of this node and its entire subtree. - inline TreeNode* clone() const { - auto* copy = new TreeNode(data, NULL); + inline NodePtr clone() const + { + auto* copy = new Node(data, NULL); for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) copy->addChild((*this)[i]->clone()); @@ -74,11 +182,13 @@ public: } template - inline TreeNode* cloneIf(Func pred) const { + inline NodePtr cloneIf(Func pred) const + { if (!pred(this)) return NULL; - auto* newNode = new TreeNode(data, NULL); + auto* newNode = new Node(data, NULL); for (U32 i = 0; i < this->size(); ++i) - if ((*this)[i]) { + if ((*this)[i]) + { auto* filteredChild = (*this)[i]->cloneIf(pred); if (filteredChild) newNode->addChild(filteredChild); @@ -86,37 +196,45 @@ public: return newNode; } - inline bool equals(const TreeNode* other) const { + inline bool equals(const NodePtr other) const + { if (!other || data != other->data || this->size() != other->size()) return false; - for (U32 i = 0; i < this->size(); ++i) { - if ((*this)[i] && other->getChild(i)) { + + for (U32 i = 0; i < this->size(); ++i) + { + if ((*this)[i] && other->getChild(i)) + { if (!(*this)[i]->equals(other->getChild(i))) return false; } - else if ((*this)[i] || other->getChild(i)) { + else if ((*this)[i] || other->getChild(i)) + { return false; } } return true; } - static inline TreeNode* mergeAt(TreeNode* atNode, TreeNode* subtree) { + static inline NodePtr mergeAt(NodePtr atNode, NodePtr subtree) + { if (!atNode || !subtree) return NULL; atNode->addChild(subtree); return subtree; } - static inline TreeNode* cloneAt(TreeNode* atNode, const TreeNode* subtree) { + static inline NodePtr cloneAt(NodePtr atNode, const NodePtr subtree) + { if (!atNode || !subtree) return NULL; - TreeNode* newClone = subtree->clone(); + NodePtr newClone = subtree->clone(); atNode->addChild(newClone); return newClone; } - inline bool isSubtreeOf(const TreeNode* other) const { + inline bool isSubtreeOf(const NodePtr other) const + { if (!other) return false; if (equals(other)) return true; for (U32 i = 0; i < other->size(); ++i) @@ -124,152 +242,197 @@ public: return true; return false; } + // Assignment operator: shallow copy of data and parent pointer only. // Does NOT copy children or subtree. - - inline TreeNode& operator=(const TreeNode& other) { + inline Node& operator=(const Node& other) + { if (this == &other) return *this; data = other.data; parent = other.parent; - // Children are NOT copied; this is a shallow assignment. return *this; } + //------------------------------------------------------------------------- // Node Type & Relationship Queries //------------------------------------------------------------------------- inline bool isRoot() const { return parent == NULL; } - inline bool isLeaf() const { + + inline bool isLeaf() const + { if constexpr (nullClears) return this->size() == 0; - else { - for (U32 i = 0; i < this->size(); ++i) { + else + { + for (U32 i = 0; i < this->size(); ++i) if ((*this)[i] != NULL) return false; - } return true; } } - inline bool hasChildren() const { + + inline bool hasChildren() const + { if constexpr (nullClears) return this->size() > 0; - else { + else + { for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) return true; return false; } } - inline U32 getNumChildren() const { + + inline U32 getNumChildren() const + { if constexpr (nullClears) return this->size(); - else { + else + { U32 count = 0; for (U32 i = 0; i < this->size(); ++i) if ((*this)[i]) ++count; return count; } } - inline bool hasChild(TreeNode* child) const { - if (!child) return false; - for (U32 i = 0; i < this->size(); i++) - if ((*this)[i] == child) - return true; - return false; + + inline bool hasChild(NodePtr child) const + { + return _containsChildPtr(child); } - inline S32 getChildIndex(TreeNode* child) const { + + inline S32 getChildIndex(NodePtr child) const + { if (!child) return -1; for (U32 i = 0; i < this->size(); i++) if ((*this)[i] == child) return (S32)i; return -1; } - inline bool isValidTree() const { - for (U32 i = 0; i < this->size(); i++) { - TreeNode* child = (*this)[i]; + + inline bool isValidTree() const + { + for (U32 i = 0; i < this->size(); i++) + { + NodePtr child = (*this)[i]; if (child && child->parent != this) return false; } return true; } - inline bool isVisited(const Vector*>& visited) const { + + inline bool isVisited(const Vector& visited) const + { for (U32 i = 0; i < visited.size(); ++i) if (visited[i] == this) return true; return false; } + template - inline TreeNode* findLeafIf(Func pred) { + inline NodePtr findLeafIf(Func pred) + { if (isLeaf() && pred(this)) return this; for (U32 i = 0; i < this->size(); ++i) - if ((*this)[i]) { + if ((*this)[i]) + { auto* found = (*this)[i]->findLeafIf(pred); if (found) return found; } return NULL; } + //------------------------------------------------------------------------- // Parent/Ancestor Logic //------------------------------------------------------------------------- - inline TreeNode* getParent() { return parent; } - inline const TreeNode* getParent() const { return parent; } - inline TreeNode* getRoot() { - TreeNode* current = this; + inline NodePtr getParent() { return parent; } + inline const NodePtr getParent() const { return parent; } + + inline NodePtr getRoot() + { + NodePtr current = this; while (current->parent) current = current->parent; return current; } - inline const TreeNode* getRoot() const { - const TreeNode* current = this; - while (current->parent) - current = current->parent; - return current; + + inline const NodePtr getRoot() const + { + const NodePtr current = const_cast(this); + NodePtr c = current; + while (c->parent) + c = c->parent; + return c; } - inline bool isAncestorOf(const TreeNode* other) const { - const TreeNode* node = other->parent; - while (node) { + + inline bool isAncestorOf(const NodePtr other) const + { + const Node* node = other ? other->parent : NULL; + while (node) + { if (node == this) return true; node = node->parent; } return false; } - inline bool isDescendantOf(const TreeNode* other) const { - return other->isAncestorOf(this); + + inline bool isDescendantOf(const NodePtr other) const + { + return other ? other->isAncestorOf(const_cast(this)) : false; } - inline TreeNode* lowestCommonAncestor(TreeNode* other) { - Vector*> pathA, pathB; + + inline NodePtr lowestCommonAncestor(NodePtr other) + { + Vector pathA, pathB; getPathToRoot(pathA); other->getPathToRoot(pathB); - TreeNode* lca = NULL; + + NodePtr lca = NULL; S32 i = pathA.size() - 1, j = pathB.size() - 1; - while (i >= 0 && j >= 0 && pathA[i] == pathB[j]) { + while (i >= 0 && j >= 0 && pathA[i] == pathB[j]) + { lca = pathA[i]; --i; --j; } return lca; } - inline void getPathToRoot(Vector*>& out) const { - const TreeNode* node = this; - while (node) { + + inline void getPathToRoot(Vector& out) const + { + NodePtr node = const_cast(this); + while (node) + { out.push_back(node); node = node->parent; } } - inline void setParent(TreeNode* p) { + + inline void setParent(NodePtr p) + { if (parent == p) return; if (parent) parent->removeChild(this); parent = p; - if (parent) parent->push_back_unique(this); + if (parent) + parent->_insertChildPtrUnique(this); } - inline void nullParent(bool orphan = true) { + + inline void nullParent(bool orphan = true) + { if (orphan && parent) parent->removeChild(this); parent = NULL; } - static inline void safeDetachFromParent(TreeNode* node) { + + static inline void safeDetachFromParent(NodePtr node) + { if (!node) return; - if (node->parent) { - for (U32 i = 0; i < node->parent->size(); ++i) { - if ((*node->parent)[i] == node) { + if (node->parent) + { + for (U32 i = 0; i < node->parent->size(); ++i) + { + if ((*node->parent)[i] == node) + { node->parent->removeChild(node); break; } @@ -277,169 +440,224 @@ public: } node->parent = NULL; } - inline void collectAncestors(Vector*>& outAncestors) const { - TreeNode* node = parent; - while (node) { + + inline void collectAncestors(Vector& outAncestors) const + { + NodePtr node = parent; + while (node) + { outAncestors.push_back_unique(node); node = node->parent; } } - inline void collectAncestorsSortedByDepth(const Vector*>& leaves, Vector*>& outAncestors) const { - Map*, bool> ancestorSet; - for (U32 i = 0; i < leaves.size(); ++i) { - TreeNode* node = leaves[i]->getParent(); - while (node) { + + inline void collectAncestorsSortedByDepth(const Vector& leaves, Vector& outAncestors) const + { + Map ancestorSet; + for (U32 i = 0; i < leaves.size(); ++i) + { + NodePtr node = leaves[i]->getParent(); + while (node) + { ancestorSet.insert(node, true); node = node->getParent(); } } + outAncestors.clear(); outAncestors.reserve(ancestorSet.size()); - for (typename Map*, bool>::Iterator iter = ancestorSet.begin(); iter != ancestorSet.end(); ++iter) + for (typename Map::Iterator iter = ancestorSet.begin(); iter != ancestorSet.end(); ++iter) outAncestors.push_back(iter->key); - outAncestors.sort([](TreeNode* a, TreeNode* b) { + + outAncestors.sort([](NodePtr a, NodePtr b) { U32 depthA = a->getDepth(); U32 depthB = b->getDepth(); return (depthA > depthB) ? -1 : (depthA < depthB) ? 1 : 0; }); } - inline U32 getDepth() const { + + inline U32 getDepth() const + { U32 depth = 0; - TreeNode* current = parent; - while (current) { + NodePtr current = parent; + while (current) + { depth++; current = current->parent; } return depth; } + template - inline void forEachAncestor(Func callback) const { - const TreeNode* node = parent; - while (node) { + inline void forEachAncestor(Func callback) const + { + const Node* node = parent; + while (node) + { callback(node); node = node->parent; } } + //------------------------------------------------------------------------- // Child Logic //------------------------------------------------------------------------- - inline TreeNode* addChild(const T& val) { - TreeNode* child = new TreeNode(val, this); - push_back_unique(child); + inline NodePtr addChild(const T& val) + { + NodePtr child = new Node(val, this); + _insertChildPtrUnique(child); return child; } - inline void addChild(TreeNode* child) { + + inline void addChild(NodePtr child) + { + AssertFatal(child != NULL, "TreeNode::addChild - Attempted to add NULL child"); + + // Prevent duplicates. for (U32 i = 0; i < this->size(); i++) AssertFatal((*this)[i] != child, "TreeNode::addChild - Attempted to add duplicate child"); + #ifdef TORQUE_DEBUG - TreeNode* ancestor = this; - while (ancestor) { + NodePtr ancestor = this; + while (ancestor) + { AssertFatal(ancestor != child, "TreeNode::addChild - Would create circular parent relationship"); ancestor = ancestor->parent; } #endif - if (child->parent && child->parent != this) { + + if (child->parent && child->parent != this) child->parent->removeChild(child); - } + child->parent = this; - push_back_unique(child); + _insertChildPtrUnique(child); } - inline void setChild(U32 i, TreeNode* child) { - if (i >= this->size()) { - U32 oldSize = this->size(); - increment((i + 1) - oldSize); - for (U32 j = oldSize; j < this->size(); j++) - (*this)[j] = NULL; + + inline void setChild(U32 i, NodePtr child) + { + if constexpr (mTFixedChildren) + { + AssertFatal(i < CHILD_COUNT, "TreeNode::setChild - Index out of bounds for fixed child TreeNode"); } - TreeNode* oldChild = (*this)[i]; + else + { + if (i >= (U32)this->size()) + { + U32 oldSize = this->size(); + this->increment((i + 1) - oldSize); + for (U32 j = oldSize; j < (U32)this->size(); j++) + (*this)[j] = NULL; + } + } + + NodePtr oldChild = (*this)[i]; if (oldChild == child) return; + if (oldChild) oldChild->parent = NULL; - if (child == NULL) { + + if (child == NULL) + { if constexpr (nullClears) this->erase(i); else (*this)[i] = NULL; return; } + #ifdef TORQUE_DEBUG - TreeNode* ancestor = this; - while (ancestor) { + NodePtr ancestor = this; + while (ancestor) + { AssertFatal(ancestor != child, "TreeNode::setChild - Would create circular parent relationship"); ancestor = ancestor->parent; } #endif - if (child->parent && child->parent != this) { + + if (child->parent && child->parent != this) + { child->parent->removeChild(child); } - else if (child->parent == this) { - for (U32 j = 0; j < this->size(); j++) { + else if (child->parent == this) + { + for (U32 j = 0; j < (U32)this->size(); j++) + { if (j != i && (*this)[j] == child) + { if constexpr (nullClears) this->erase(j); else (*this)[j] = NULL; + } } } - for (U32 j = 0; j < this->size(); j++) + + for (U32 j = 0; j < (U32)this->size(); j++) AssertFatal(j == i || (*this)[j] != child, "TreeNode::setChild - Attempted to set duplicate child"); + (*this)[i] = child; child->parent = this; } - inline TreeNode* getChild(U32 i) { - if (i < this->size()) { - if ((*this)[i] && (*this)[i]->parent != this) { - AssertFatal(false, "TreeNode::getChild - Child parent pointer mismatch"); - } - return (*this)[i]; - } - return NULL; - } - inline const TreeNode* getChild(U32 i) const { - if (i < this->size()) { - if ((*this)[i] && (*this)[i]->parent != this) { - AssertFatal(false, "TreeNode::getChild - Child parent pointer mismatch"); - } - return (*this)[i]; - } - return NULL; - } - inline Vector*> getChildren() const + + inline NodePtr getChild(U32 i) { - Vector*> children; - for (U32 i = 0; i < this->size(); ++i) { + if (i < (U32)this->size()) + { + if ((*this)[i] && (*this)[i]->parent != this) + AssertFatal(false, "TreeNode::getChild - Child parent pointer mismatch"); + return (*this)[i]; + } + return NULL; + } + + inline const NodePtr getChild(U32 i) const + { + if (i < (U32)this->size()) + { + if ((*this)[i] && (*this)[i]->parent != this) + AssertFatal(false, "TreeNode::getChild - Child parent pointer mismatch"); + return (*this)[i]; + } + return NULL; + } + + inline Vector getChildren() const + { + Vector children; + for (U32 i = 0; i < (U32)this->size(); ++i) if ((*this)[i]) children.push_back((*this)[i]); - } return children; } - inline void addChildren(const Vector*>* children) { + inline void addChildren(const Vector* children) + { if (!children) return; - for (U32 i = 0; i < children->size(); i++) { - TreeNode* child = (*children)[i]; + for (U32 i = 0; i < (U32)children->size(); i++) + { + NodePtr child = (*children)[i]; if (!child) continue; - bool isDuplicate = false; - for (U32 j = 0; j < this->size(); j++) { - if ((*this)[j] == child) { - isDuplicate = true; - break; - } - } - if (isDuplicate) + + if (_containsChildPtr(child)) continue; + #ifdef TORQUE_DEBUG - TreeNode* ancestor = this; - while (ancestor) { + NodePtr ancestor = this; + while (ancestor) + { AssertFatal(ancestor != child, "TreeNode::addChildren - Would create circular parent relationship"); ancestor = ancestor->parent; } #endif - if (child->parent && child->parent != this) { + + if (child->parent && child->parent != this) + { bool foundInParent = false; - for (U32 k = 0; k < child->parent->size(); ++k) { - if ((*child->parent)[k] == child) { + for (U32 k = 0; k < (U32)child->parent->size(); ++k) + { + if ((*child->parent)[k] == child) + { foundInParent = true; break; } @@ -449,58 +667,80 @@ public: else child->parent = NULL; } + if (child->parent == this) continue; + child->parent = this; - push_back_unique(child); + _insertChildPtrUnique(child); } } - inline void clearChildren() { - for (U32 i = 0; i < this->size(); i++) { - TreeNode* child = (*this)[i]; - if (child) { - if (child->parent != this) { + + inline void clearChildren() + { + for (U32 i = 0; i < (U32)this->size(); i++) + { + NodePtr child = (*this)[i]; + if (child) + { + if (child->parent != this) + { #ifdef TORQUE_DEBUG Con::warnf("TreeNode::clearChildren - Child at index %u has incorrect parent pointer (corruption detected)", i); #endif child->parent = NULL; } - else { + else + { child->parent = NULL; } } } - this->clear(); + + _clearAllChildSlots(); } - inline void deleteChildren() { - Vector*> children; - for (U32 i = 0; i < this->size(); i++) { - TreeNode* child = (*this)[i]; - if (child) { - if (child->parent != this) { + + inline void deleteChildren() + { + Vector children; + for (U32 i = 0; i < (U32)this->size(); i++) + { + NodePtr child = (*this)[i]; + if (child) + { + if (child->parent != this) + { #ifdef TORQUE_DEBUG Con::warnf("TreeNode::deleteChildren - Child at index %u has incorrect parent pointer (corruption detected)", i); #endif child->parent = NULL; } - else { + else + { child->parent = NULL; } } children.push_back_unique(child); } - this->clear(); - for (U32 i = 0; i < children.size(); i++) { - if (children[i]) { + + _clearAllChildSlots(); + + for (U32 i = 0; i < (U32)children.size(); i++) + { + if (children[i]) delete children[i]; - } } } - inline void removeChild(TreeNode* child) { + + inline void removeChild(NodePtr child) + { AssertFatal(child != NULL, "TreeNode::removeChild - Attempted to remove NULL child"); bool found = false; - for (U32 i = 0; i < this->size(); i++) { - if ((*this)[i] == child) { + + for (U32 i = 0; i < (U32)this->size(); i++) + { + if ((*this)[i] == child) + { child->parent = NULL; if constexpr (nullClears) this->erase(i); @@ -510,10 +750,14 @@ public: break; } } - if (!found) { + + if (!found) + { #ifdef TORQUE_DEBUG - for (U32 i = 0; i < this->size(); i++) { - if ((*this)[i] && (*this)[i]->parent == this) { + for (U32 i = 0; i < (U32)this->size(); i++) + { + if ((*this)[i] && (*this)[i]->parent == this) + { (*this)[i]->parent = NULL; if constexpr (nullClears) this->erase(i); @@ -528,41 +772,53 @@ public: Con::errorf("TreeNode::removeChild - Child not found in parent's children array (corruption detected)"); #endif } + AssertFatal(found, "TreeNode::removeChild - Child not found in parent's children array"); } - inline void removeChild(U32 i) { - AssertFatal(i < this->size(), "TreeNode::removeChild - Index out of bounds"); - TreeNode* child = (*this)[i]; + + inline void removeChild(U32 i) + { + AssertFatal(i < (U32)this->size(), "TreeNode::removeChild - Index out of bounds"); + NodePtr child = (*this)[i]; if (child) child->parent = NULL; + if constexpr (nullClears) this->erase(i); else (*this)[i] = NULL; } - inline void replaceChild(TreeNode* oldChild, TreeNode* newChild) { + + inline void replaceChild(NodePtr oldChild, NodePtr newChild) + { AssertFatal(oldChild != NULL, "TreeNode::replaceChild - Attempted to replace NULL child"); S32 idx = getChildIndex(oldChild); AssertFatal(idx != -1, "TreeNode::replaceChild - oldChild not found in children"); setChild((U32)idx, newChild); } - inline void nullChild(U32 i) { - if (i >= this->size()) + + inline void nullChild(U32 i) + { + if (i >= (U32)this->size()) return; - TreeNode* child = (*this)[i]; + NodePtr child = (*this)[i]; if (child && child->parent == this) child->parent = NULL; (*this)[i] = NULL; } - inline void swapChildren(U32 i, U32 j) { - if (i >= this->size() || j >= this->size() || i == j) + + inline void swapChildren(U32 i, U32 j) + { + if (i >= (U32)this->size() || j >= (U32)this->size() || i == j) return; - TreeNode* childI = (*this)[i]; - TreeNode* childJ = (*this)[j]; + + NodePtr childI = (*this)[i]; + NodePtr childJ = (*this)[j]; if (childI && childI->parent != this) childI->parent = this; if (childJ && childJ->parent != this) childJ->parent = this; + (*this)[i] = childJ; (*this)[j] = childI; } @@ -570,12 +826,14 @@ public: //------------------------------------------------------------------------- // Sibling Logic //------------------------------------------------------------------------- - inline Vector*> getSiblings() const { - Vector*> siblings; + inline Vector getSiblings() const + { + Vector siblings; if (!parent) return siblings; siblings.reserve(parent->size() - 1); - for (U32 i = 0; i < parent->size(); i++) { - TreeNode* sibling = (*parent)[i]; + for (U32 i = 0; i < (U32)parent->size(); i++) + { + NodePtr sibling = (*parent)[i]; if (sibling && sibling != this) siblings.push_back_unique(sibling); } @@ -585,143 +843,167 @@ public: //------------------------------------------------------------------------- // Traversal & Collection //------------------------------------------------------------------------- - inline void findLeaves(Vector*>& out) { + inline void findLeaves(Vector& out) + { if (isLeaf()) out.push_back_unique(this); else - for (U32 i = 0; i < this->size(); ++i) + for (U32 i = 0; i < (U32)this->size(); ++i) if ((*this)[i]) (*this)[i]->getLeaves(out); } - inline void collectSubtree(Vector*>& nodes) { + + inline void collectSubtree(Vector& nodes) + { nodes.push_back_unique(this); - for (U32 i = 0; i < this->size(); ++i) + for (U32 i = 0; i < (U32)this->size(); ++i) if ((*this)[i]) (*this)[i]->collectSubtree(nodes); } - inline void collectSubtreeData(Vector& dataList) { + + inline void collectSubtreeData(Vector& dataList) + { dataList.push_back_unique(data); - for (U32 i = 0; i < this->size(); ++i) + for (U32 i = 0; i < (U32)this->size(); ++i) if ((*this)[i]) (*this)[i]->collectSubtreeData(dataList); } - // callback signature: void(TreeNode*) + template - inline void forEachChild(Func callback) { - for (U32 i = 0; i < this->size(); i++) + inline void forEachChild(Func callback) + { + for (U32 i = 0; i < (U32)this->size(); i++) if ((*this)[i]) callback((*this)[i]); } - // callback signature: void(const TreeNode*) + template - inline void forEachChild(Func callback) const { - for (U32 i = 0; i < this->size(); i++) + inline void forEachChild(Func callback) const + { + for (U32 i = 0; i < (U32)this->size(); i++) if ((*this)[i]) callback((*this)[i]); } - // callback signature: void(TreeNode*) + template - inline void forEachInSubtree(Func callback) { + inline void forEachInSubtree(Func callback) + { callback(this); - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < (U32)this->size(); i++) if ((*this)[i]) (*this)[i]->forEachInSubtree(callback); } - // callback signature: void(const TreeNode*) + template - inline void forEachLeaf(Func callback) { + inline void forEachLeaf(Func callback) + { if (isLeaf()) callback(this); else - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < (U32)this->size(); i++) if ((*this)[i]) (*this)[i]->forEachLeaf(callback); } - // callback signature: void(TreeNode*) + template - inline void traverseBreadthFirst(Func callback) { - Vector*> queue; + inline void traverseBreadthFirst(Func callback) + { + Vector queue; queue.push_back(this); - while (!queue.empty()) { - TreeNode* node = queue.first(); + while (!queue.empty()) + { + NodePtr node = queue.first(); queue.erase(0); callback(node); - for (U32 i = 0; i < node->size(); ++i) + for (U32 i = 0; i < (U32)node->size(); ++i) if ((*node)[i]) queue.push_back((*node)[i]); } } - // Calls the provided functor on each ancestor up to the root (excluding this node). - // Typically used for propagating updates up the tree (e.g., refitting bounds). - // callback signature: void(TreeNode*) + template - inline void refitPathToRoot(Func refitFunc) { - TreeNode* node = parent; - while (node) { + inline void refitPathToRoot(Func refitFunc) + { + NodePtr node = parent; + while (node) + { refitFunc(node); node = node->parent; } } - // callback signature: TreeNode* pred(TreeNode*) + template - inline TreeNode* findIf(Func pred) { + inline NodePtr findIf(Func pred) + { if (pred(this)) return this; - for (U32 i = 0; i < this->size(); ++i) - if ((*this)[i]) { + for (U32 i = 0; i < (U32)this->size(); ++i) + if ((*this)[i]) + { auto* found = (*this)[i]->findIf(pred); if (found) return found; } return NULL; } - // callback signature: const TreeNode* pred(const TreeNode*) + template - inline void findAllIf(Func pred, Vector*>& out) { + inline void findAllIf(Func pred, Vector& out) + { if (pred(this)) out.push_back(this); - for (U32 i = 0; i < this->size(); ++i) + for (U32 i = 0; i < (U32)this->size(); ++i) if ((*this)[i]) (*this)[i]->findAllIf(pred, out); } - // callback signature: const TreeNode* pred(const TreeNode*) + template - inline U32 countIf(Func pred) const { + inline U32 countIf(Func pred) const + { U32 count = pred(this) ? 1 : 0; - for (U32 i = 0; i < this->size(); ++i) + for (U32 i = 0; i < (U32)this->size(); ++i) if ((*this)[i]) count += (*this)[i]->countIf(pred); return count; } - // callback signature: bool pred(TreeNode*) + template - inline void removeIf(Func pred) { - for (S32 i = this->size() - 1; i >= 0; --i) { - if ((*this)[i] && pred((*this)[i])) { + inline void removeIf(Func pred) + { + for (S32 i = this->size() - 1; i >= 0; --i) + { + if ((*this)[i] && pred((*this)[i])) + { delete (*this)[i]; if constexpr (nullClears) this->erase(i); else (*this)[i] = NULL; } - else if ((*this)[i]) { + else if ((*this)[i]) + { (*this)[i]->removeIf(pred); } } } + //------------------------------------------------------------------------- // Tree Metrics //------------------------------------------------------------------------- - inline U32 getTreeSize() const { + inline U32 getTreeSize() const + { U32 count = 1; - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < (U32)this->size(); i++) if ((*this)[i]) count += (*this)[i]->getTreeSize(); return count; } - inline U32 getMaxDepth() const { + + inline U32 getMaxDepth() const + { if (isLeaf()) return 0; U32 maxDepth = 0; - for (U32 i = 0; i < this->size(); i++) - if ((*this)[i]) { + for (U32 i = 0; i < (U32)this->size(); i++) + if ((*this)[i]) + { U32 childDepth = (*this)[i]->getMaxDepth(); if (childDepth > maxDepth) maxDepth = childDepth; @@ -732,27 +1014,33 @@ public: //------------------------------------------------------------------------- // Rotation/Balance Utilities //------------------------------------------------------------------------- - static inline U32 gatherRotationCandidates(TreeNode* parent, Vector*>& outNodes, U32 maxCandidates = 8) { + static inline U32 gatherRotationCandidates(NodePtr parent, Vector& outNodes, U32 maxCandidates = 8) + { outNodes.clear(); - for (U32 i = 0; i < parent->size() && outNodes.size() < maxCandidates; ++i) { - TreeNode* child = (*parent)[i]; + for (U32 i = 0; i < (U32)parent->size() && outNodes.size() < maxCandidates; ++i) + { + NodePtr child = (*parent)[i]; if (!child) continue; - if (child->isLeaf()) { + if (child->isLeaf()) + { outNodes.push_back_unique(child); } - else { - for (U32 j = 0; j < child->size() && outNodes.size() < maxCandidates; ++j) { - TreeNode* grandChild = (*child)[j]; + else + { + for (U32 j = 0; j < (U32)child->size() && outNodes.size() < maxCandidates; ++j) + { + NodePtr grandChild = (*child)[j]; if (grandChild) outNodes.push_back_unique(grandChild); } } } return outNodes.size(); } + static inline bool findBestRotationPairing( - const Vector*>& nodes, + const Vector& nodes, U32 groupSize, - F32(*costFunc)(const TreeNode*, const TreeNode*), + F32(*costFunc)(const Node*, const Node*), Vector& outGroupA, Vector& outGroupB, F32& outBestCost) @@ -761,39 +1049,53 @@ public: if (n < 2 * groupSize) return false; outBestCost = F32_MAX; bool found = false; + Vector indices; indices.setSize(n); for (U32 i = 0; i < n; ++i) indices[i] = i; + Vector groupA, groupB; groupA.setSize(groupSize); groupB.setSize(groupSize); - while (true) { + + while (true) + { for (U32 i = 0; i < groupSize; ++i) groupA[i] = indices[i]; + U32 bIdx = 0; for (U32 i = groupSize; i < n; ++i) groupB[bIdx++] = indices[i]; - if (bIdx == groupSize) { + + if (bIdx == groupSize) + { F32 costA = costFunc(nodes[groupA[0]], nodes[groupA[1]]); F32 costB = costFunc(nodes[groupB[0]], nodes[groupB[1]]); F32 totalCost = costA + costB; - if (totalCost < outBestCost) { + if (totalCost < outBestCost) + { outBestCost = totalCost; outGroupA = groupA; outGroupB = groupB; found = true; } } + S32 k = n - 2; while (k >= 0 && indices[k] >= indices[k + 1]) --k; if (k < 0) break; + S32 l = n - 1; while (indices[k] >= indices[l]) --l; + U32 tmp = indices[k]; indices[k] = indices[l]; indices[l] = tmp; - for (U32 i = k + 1, j = n - 1; i < j; ++i, --j) { + + for (U32 i = k + 1, j = n - 1; i < j; ++i, --j) + { tmp = indices[i]; indices[i] = indices[j]; indices[j] = tmp; } } + return found; } @@ -813,37 +1115,45 @@ public: inline Derived* getChildAs(U32 i) { return static_cast(getChild(i)); } template inline const Derived* getChildAs(U32 i) const { return static_cast(getChild(i)); } + template - inline Vector getChildrenAs() { + inline Vector getChildrenAs() + { Vector out; - for (U32 i = 0; i < this->size(); ++i) + for (U32 i = 0; i < (U32)this->size(); ++i) if ((*this)[i]) out.push_back(static_cast((*this)[i])); return out; } + template - inline Vector getChildrenAs() const { + inline Vector getChildrenAs() const + { Vector out; - for (U32 i = 0; i < this->size(); ++i) + for (U32 i = 0; i < (U32)this->size(); ++i) if ((*this)[i]) out.push_back(static_cast((*this)[i])); return out; } + template - inline void forEachLeafAs(Func callback) { + inline void forEachLeafAs(Func callback) + { if (isLeaf()) callback(static_cast(this)); else - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < (U32)this->size(); i++) if ((*this)[i]) (*this)[i]->template forEachLeafAs(callback); } + template - inline void forEachLeafAs(Func callback) const { + inline void forEachLeafAs(Func callback) const + { if (isLeaf()) callback(static_cast(this)); else - for (U32 i = 0; i < this->size(); i++) + for (U32 i = 0; i < (U32)this->size(); i++) if ((*this)[i]) (*this)[i]->template forEachLeafAs(callback); } From fc8c3531cbe083f2055d99fadedee3df4e3b26d4 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Wed, 13 May 2026 17:54:25 -0500 Subject: [PATCH 16/17] mac fix, needed to tell it yes, we'll never have a >32 bit offset for a member variable typofix in tree. which...somehow compiled on windows anyway... will have to hunt that down.. --- Engine/source/core/util/tVector.h | 4 ++-- Engine/source/core/util/treeObject.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Engine/source/core/util/tVector.h b/Engine/source/core/util/tVector.h index b14388cd9..9cda3c050 100644 --- a/Engine/source/core/util/tVector.h +++ b/Engine/source/core/util/tVector.h @@ -1012,7 +1012,7 @@ public: return EngineFieldTable::Field{ "elementCount", "", 1, TYPE(((ThisType*)16)->mState.mElementCount), - FIELDOFFSET(mState.mElementCount) + static_cast(FIELDOFFSET(mState.mElementCount)) }; } else @@ -1028,7 +1028,7 @@ public: return EngineFieldTable::Field{ "arraySize", "", 1, TYPE(((ThisType*)16)->mState.mArraySize), - FIELDOFFSET(mState.mArraySize) + static_cast(FIELDOFFSET(mState.mArraySize)) }; } else diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index 73c6e1ec1..77c13fac2 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -150,7 +150,7 @@ public: } else { - const U32 n = getMin(children.size(), CHILD_COUNT); + const U32 n = getMin(children.size(), CHILD_COUNT); for (U32 i = 0; i < n; ++i) { (*this)[i] = children[i]; From 7d65976c319a155069130f4626a475e61bf395ca Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sat, 30 May 2026 11:56:45 -0500 Subject: [PATCH 17/17] licence updates --- Engine/source/core/util/tVector.h | 2 +- Engine/source/core/util/treeObject.cpp | 3 ++- Engine/source/core/util/treeObject.h | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Engine/source/core/util/tVector.h b/Engine/source/core/util/tVector.h index 9cda3c050..f82030909 100644 --- a/Engine/source/core/util/tVector.h +++ b/Engine/source/core/util/tVector.h @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC -// +// Portions Copyright (c) 2025 Ratfish Studios, 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 diff --git a/Engine/source/core/util/treeObject.cpp b/Engine/source/core/util/treeObject.cpp index 7aca2cfa4..3a1976388 100644 --- a/Engine/source/core/util/treeObject.cpp +++ b/Engine/source/core/util/treeObject.cpp @@ -1,5 +1,6 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2012 GarageGames, LLC +// Copyright (c) 2025 Ratfish Studios, LLC +// Portions 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 diff --git a/Engine/source/core/util/treeObject.h b/Engine/source/core/util/treeObject.h index 77c13fac2..6c2129b8a 100644 --- a/Engine/source/core/util/treeObject.h +++ b/Engine/source/core/util/treeObject.h @@ -1,5 +1,6 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2012 GarageGames, LLC +// Copyright (c) 2025 Ratfish Studios, LLC +// Portions 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