mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-13 15:44:36 +00:00
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
This commit is contained in:
parent
09f0fa2861
commit
8ec65267ea
2 changed files with 622 additions and 191 deletions
|
|
@ -26,9 +26,20 @@ IMPLEMENT_CONOBJECT(TreeObject);
|
||||||
|
|
||||||
TreeObject::~TreeObject()
|
TreeObject::~TreeObject()
|
||||||
{
|
{
|
||||||
if (mRoot != NULL)
|
if (mRoot)
|
||||||
_deleteNode(mRoot, false);
|
{
|
||||||
|
delete mRoot;
|
||||||
|
mRoot = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up any orphaned nodes (safety measure)
|
||||||
|
for (Map<S32, Node*>::Iterator i = mKeyMap.begin(); i != mKeyMap.end(); ++i)
|
||||||
|
{
|
||||||
|
if (i->value && i->value->parent == NULL && i->value != mRoot)
|
||||||
|
{
|
||||||
|
delete i->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
mKeyMap.clear();
|
mKeyMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,42 +143,46 @@ S32 TreeObject::addNode(S32 parentKey, const char* scriptData, S32 forcedKey)
|
||||||
void TreeObject::deleteNode(S32 key)
|
void TreeObject::deleteNode(S32 key)
|
||||||
{
|
{
|
||||||
Node* node = findNode(key);
|
Node* node = findNode(key);
|
||||||
if (node == NULL)
|
if (!node)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (node->parent != NULL)
|
|
||||||
{
|
|
||||||
Node* parentNode = static_cast<Node*>(node->parent);
|
|
||||||
for (S32 i = 0; i < parentNode->size(); i++)
|
|
||||||
{
|
|
||||||
if ((*parentNode)[i] == static_cast<TreeNode<void*>*>(node))
|
|
||||||
{
|
|
||||||
parentNode->erase(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (node == mRoot)
|
if (node == mRoot)
|
||||||
|
{
|
||||||
mRoot = NULL;
|
mRoot = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
_deleteNode(node, true);
|
_deleteNode(node, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TreeObject::_deleteNode(Node* node, bool unlinkFromMap)
|
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<S32> keysToRemove;
|
||||||
|
if (unlinkFromMap)
|
||||||
{
|
{
|
||||||
Node* child = static_cast<Node*>((*node)[i]);
|
node->forEachInSubtree([&keysToRemove](TreeNode<void*>* n) {
|
||||||
_deleteNode(child, unlinkFromMap);
|
Node* scriptNode = static_cast<Node*>(n);
|
||||||
|
keysToRemove.push_back(scriptNode->key);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlinkFromMap) {
|
// Remove from parent's children array
|
||||||
mKeyMap.erase(node->key);
|
node->nullParent();
|
||||||
}
|
|
||||||
|
// Delete the node (destructor handles children recursively)
|
||||||
if (node == mRoot) mRoot = NULL;
|
|
||||||
delete node;
|
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)
|
TreeObject::Node* TreeObject::findNode(S32 key)
|
||||||
|
|
@ -192,6 +207,7 @@ bool TreeObject::toParent(S32 key, S32 newParentKey)
|
||||||
if (!targetNode || !newParentNode || key == newParentKey)
|
if (!targetNode || !newParentNode || key == newParentKey)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Check for circular reference (prevent making ancestor a child)
|
||||||
Node* checkNode = newParentNode;
|
Node* checkNode = newParentNode;
|
||||||
while (checkNode != NULL)
|
while (checkNode != NULL)
|
||||||
{
|
{
|
||||||
|
|
@ -199,17 +215,9 @@ bool TreeObject::toParent(S32 key, S32 newParentKey)
|
||||||
return false;
|
return false;
|
||||||
checkNode = static_cast<Node*>(checkNode->parent);
|
checkNode = static_cast<Node*>(checkNode->parent);
|
||||||
}
|
}
|
||||||
if (targetNode->parent)
|
|
||||||
{
|
// Remove from old parent and add to new parent
|
||||||
TreeNode<void*>* oldParent = targetNode->parent;
|
targetNode->nullParent();
|
||||||
for (S32 i = 0; i < oldParent->size(); i++) {
|
|
||||||
if ((*oldParent)[i] == targetNode) {
|
|
||||||
oldParent->erase(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
targetNode->parent = newParentNode;
|
targetNode->parent = newParentNode;
|
||||||
newParentNode->push_back(targetNode);
|
newParentNode->push_back(targetNode);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,239 +27,533 @@
|
||||||
#include "core/util/tDictionary.h"
|
#include "core/util/tDictionary.h"
|
||||||
#include "console/consoleTypes.h"
|
#include "console/consoleTypes.h"
|
||||||
|
|
||||||
template <class T>
|
// Add nullClears as a template argument (default: true. set false for BVH/quad/octrees)
|
||||||
class TreeNode : public Vector<TreeNode<T>*> {
|
template <class T, bool nullClears = true>
|
||||||
|
class TreeNode : public Vector<TreeNode<T, nullClears>*> {
|
||||||
public:
|
public:
|
||||||
T data;
|
T data;
|
||||||
TreeNode<T>* parent;
|
TreeNode<T, nullClears>* parent;
|
||||||
TreeNode(const T& val = T(), TreeNode<T>* p = NULL) : data(val), parent(p) {}
|
TreeNode(const T& val = T(), TreeNode<T, nullClears>* p = NULL) : data(val), parent(p) {}
|
||||||
|
TreeNode(const Vector<TreeNode<T, nullClears>*>& children, TreeNode<T, nullClears>* 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()
|
virtual ~TreeNode()
|
||||||
{
|
{
|
||||||
deleteChildren();
|
deleteChildren();
|
||||||
if (parent)
|
parent = NULL;
|
||||||
{
|
|
||||||
for (U32 i = 0; i < parent->size(); i++)
|
|
||||||
{
|
|
||||||
if ((*parent)[i] == this)
|
|
||||||
{
|
|
||||||
parent->erase(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parent = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//description logic
|
//description logic
|
||||||
inline bool isRoot() const { return parent == NULL; }
|
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
|
// Parent logic
|
||||||
inline TreeNode<T>* getParent() const { return parent; }
|
inline TreeNode<T, nullClears>* getParent() const { return parent; }
|
||||||
|
|
||||||
inline void setParent(TreeNode<T>* p)
|
inline void setParent(TreeNode<T, nullClears>* p)
|
||||||
{
|
{
|
||||||
if (parent == p) return;
|
if (parent == p) return;
|
||||||
|
|
||||||
if (parent) parent->removeChild(this);
|
if (parent) parent->removeChild(this);
|
||||||
parent = p;
|
parent = p;
|
||||||
if (parent) parent->push_back(this);
|
if (parent) parent->push_back_unique(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
//children logic
|
//children logic
|
||||||
inline TreeNode<T>* addChild(const T& val)
|
inline TreeNode<T, nullClears>* addChild(const T& val)
|
||||||
{
|
{
|
||||||
TreeNode<T>* child = new TreeNode<T>(val, this);
|
TreeNode<T, nullClears>* child = new TreeNode<T, nullClears>(val, this);
|
||||||
push_back(child);
|
this->push_back_unique(child);
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
inline void addChild(TreeNode<T>* child)
|
|
||||||
{
|
|
||||||
if (!child) return;
|
|
||||||
|
|
||||||
// Check if already a child to prevent duplicates
|
inline void addChild(TreeNode<T, nullClears>* child)
|
||||||
for (U32 i = 0; i < size(); i++)
|
{
|
||||||
{
|
// Prevent duplicate child
|
||||||
if ((*this)[i] == child)
|
for (U32 i = 0; i < this->size(); i++)
|
||||||
return; // Already a child
|
AssertFatal((*this)[i] != child, "TreeNode::addChild - Attempted to add duplicate child");
|
||||||
|
|
||||||
|
#ifdef TORQUE_DEBUG
|
||||||
|
// Prevent cycles
|
||||||
|
TreeNode<T, nullClears>* 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;
|
child->parent = this;
|
||||||
push_back(child);
|
this->push_back_unique(child);
|
||||||
}
|
}
|
||||||
inline void setChild(U32 i, TreeNode<T>* child)
|
|
||||||
|
inline void setChild(U32 i, TreeNode<T, nullClears>* child)
|
||||||
{
|
{
|
||||||
if (i >= size())
|
if (i >= this->size())
|
||||||
{
|
{
|
||||||
U32 oldSize = size();
|
U32 oldSize = this->size();
|
||||||
increment((i + 1) - oldSize);
|
this->increment((i + 1) - oldSize);
|
||||||
for (U32 j = oldSize; j < size(); j++)
|
for (U32 j = oldSize; j < this->size(); j++)
|
||||||
(*this)[j] = NULL;
|
(*this)[j] = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode<T>* oldChild = (*this)[i];
|
TreeNode<T, nullClears>* oldChild = (*this)[i];
|
||||||
if (oldChild == child) return;
|
if (oldChild == child) return;
|
||||||
|
|
||||||
// Unlink old child
|
// Unlink old child
|
||||||
if (oldChild)
|
if (oldChild)
|
||||||
{
|
|
||||||
oldChild->parent = NULL;
|
oldChild->parent = NULL;
|
||||||
}
|
|
||||||
|
|
||||||
|
// If forcing to NULL, just clear the slot and return
|
||||||
if (child == NULL)
|
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;
|
#ifdef TORQUE_DEBUG
|
||||||
child->parent = this;
|
// Prevent cycles
|
||||||
|
TreeNode<T, nullClears>* 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<T>* getChild(U32 i) const
|
inline TreeNode<T, nullClears>* 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 (*this)[i];
|
||||||
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void addChildren(const Vector<T>* children)
|
|
||||||
|
inline void addChildren(const Vector<TreeNode<T, nullClears>*>* children)
|
||||||
{
|
{
|
||||||
if (!children) return;
|
if (!children) return;
|
||||||
for (U32 i = 0; i < children->size(); i++)
|
for (U32 i = 0; i < children->size(); i++)
|
||||||
{
|
{
|
||||||
TreeNode<T>* child = new TreeNode<T>((*children)[i], this);
|
TreeNode<T, nullClears>* child = (*children)[i];
|
||||||
push_back(child);
|
|
||||||
|
// 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<T, nullClears>* 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<T>* 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<T, nullClears>* child = (*this)[i];
|
||||||
|
if (child && child->parent != this)
|
||||||
|
return false; // Child doesn't point back to parent
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool hasChild(TreeNode<T, nullClears>* 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<T, nullClears>* 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<T, nullClears>* 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<T, nullClears>* childI = (*this)[i];
|
||||||
|
TreeNode<T, nullClears>* 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<T, nullClears>* 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<T, nullClears>* 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<typename Func>
|
||||||
|
inline void forEachChild(Func callback)
|
||||||
|
{
|
||||||
|
for (U32 i = 0; i < this->size(); i++)
|
||||||
|
{
|
||||||
|
if ((*this)[i])
|
||||||
|
callback((*this)[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Func>
|
||||||
|
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<typename Func>
|
||||||
|
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<TreeNode<T, nullClears>*>& outLeaves, bool checkNULLs = true)
|
||||||
|
{
|
||||||
|
forEachLeaf([&outLeaves](TreeNode<T, nullClears>* 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<T, nullClears>* 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)
|
if ((*this)[i] == child)
|
||||||
{
|
{
|
||||||
child->parent = NULL;
|
child->parent = NULL;
|
||||||
erase(i);
|
if constexpr (nullClears)
|
||||||
return;
|
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)
|
inline void removeChild(U32 i)
|
||||||
{
|
{
|
||||||
if (i < size())
|
AssertFatal(i < this->size(), "TreeNode::removeChild - Index out of bounds");
|
||||||
{
|
TreeNode<T, nullClears>* child = (*this)[i];
|
||||||
TreeNode<T>* child = (*this)[i];
|
if (child)
|
||||||
if (child)
|
child->parent = NULL;
|
||||||
child->parent = NULL;
|
if constexpr (nullClears)
|
||||||
erase(i);
|
this->erase(i);
|
||||||
}
|
else
|
||||||
|
(*this)[i] = NULL;
|
||||||
}
|
}
|
||||||
inline void replaceChild(TreeNode<T>* oldChild, TreeNode<T>* newChild)
|
|
||||||
|
inline void replaceChild(TreeNode<T, nullClears>* oldChild, TreeNode<T, nullClears>* newChild)
|
||||||
{
|
{
|
||||||
if (!oldChild) return;
|
AssertFatal(oldChild != NULL, "TreeNode::replaceChild - Attempted to replace NULL child");
|
||||||
|
|
||||||
for (U32 i = 0; i < size(); i++)
|
S32 idx = getChildIndex(oldChild);
|
||||||
{
|
AssertFatal(idx != -1, "TreeNode::replaceChild - oldChild not found in children");
|
||||||
if ((*this)[i] == oldChild)
|
|
||||||
{
|
|
||||||
oldChild->parent = NULL;
|
|
||||||
|
|
||||||
if (newChild)
|
// Use hardened setChild for all safety checks and pointer management
|
||||||
{
|
setChild((U32)idx, 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()
|
inline void compact()
|
||||||
{
|
{
|
||||||
// Remove NULL entries from children array
|
for (U32 i = 0; i < this->size(); )
|
||||||
for (U32 i = 0; i < size(); )
|
|
||||||
{
|
{
|
||||||
if ((*this)[i] == NULL)
|
TreeNode<T, nullClears>* child = (*this)[i];
|
||||||
erase(i);
|
if constexpr (nullClears)
|
||||||
else
|
{
|
||||||
|
// 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++;
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// BVH/slot-stable style: leave NULLs, just repair parent pointers.
|
||||||
|
if (child && child->parent != this)
|
||||||
|
child->parent = this;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator =(Vector<T>* other) { clear(); addChildren(other); }
|
inline bool hasChildren() const
|
||||||
|
|
||||||
inline U32 getNumChildren() const { return size(); }
|
|
||||||
inline bool hasChildren() const { return size() > 0; }
|
|
||||||
inline Vector<TreeNode<T>*> getChildren() const
|
|
||||||
{
|
{
|
||||||
Vector<TreeNode<T>*> children;
|
if constexpr (nullClears)
|
||||||
children.reserve(size());
|
return this->size() > 0;
|
||||||
for (U32 i = 0; i < size(); i++)
|
else {
|
||||||
{
|
for (U32 i = 0; i < this->size(); ++i)
|
||||||
if ((*this)[i]) // Skip NULLs
|
if ((*this)[i]) return true;
|
||||||
children.push_back((*this)[i]);
|
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<TreeNode<T, nullClears>*> getChildren() const
|
||||||
|
{
|
||||||
|
Vector<TreeNode<T, nullClears>*> 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;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void deleteChildren()
|
inline void deleteChildren()
|
||||||
{
|
{
|
||||||
Vector<TreeNode<T>*> children;
|
Vector<TreeNode<T, nullClears>*> children;
|
||||||
for (U32 i = 0; i < size(); i++)
|
for (U32 i = 0; i < this->size(); i++)
|
||||||
{
|
{
|
||||||
children.push_back((*this)[i]);
|
TreeNode<T, nullClears>* 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();
|
clear();
|
||||||
|
|
||||||
|
|
@ -267,24 +561,50 @@ public:
|
||||||
{
|
{
|
||||||
if (children[i])
|
if (children[i])
|
||||||
{
|
{
|
||||||
children[i]->parent = NULL;
|
// Child should already be detached
|
||||||
delete children[i];
|
delete children[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//sibling logic
|
inline void collectSubtree(Vector<TreeNode<T, nullClears>*>& nodes)
|
||||||
inline Vector<TreeNode<T>*> getSiblings() const
|
|
||||||
{
|
{
|
||||||
Vector<TreeNode<T>*> siblings;
|
// Pre-order collection of all nodes in subtree
|
||||||
|
forEachInSubtree([&nodes](TreeNode<T, nullClears>* n) {
|
||||||
|
nodes.push_back_unique(n);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void collectSubtreeData(Vector<T>& dataList)
|
||||||
|
{
|
||||||
|
// Collect just the data from subtree
|
||||||
|
forEachInSubtree([&dataList](TreeNode<T, nullClears>* n) {
|
||||||
|
dataList.push_back_unique(n->data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void collectAncestors(Vector<TreeNode<T, nullClears>*>& outAncestors) const
|
||||||
|
{
|
||||||
|
TreeNode<T, nullClears>* node = parent;
|
||||||
|
while (node)
|
||||||
|
{
|
||||||
|
outAncestors.push_back_unique(node);
|
||||||
|
node = node->parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//sibling logic
|
||||||
|
inline Vector<TreeNode<T, nullClears>*> getSiblings() const
|
||||||
|
{
|
||||||
|
Vector<TreeNode<T, nullClears>*> siblings;
|
||||||
if (!parent) return siblings;
|
if (!parent) return siblings;
|
||||||
|
|
||||||
siblings.reserve(parent->size() - 1);
|
siblings.reserve(parent->size() - 1);
|
||||||
for (U32 i = 0; i < parent->size(); i++)
|
for (U32 i = 0; i < parent->size(); i++)
|
||||||
{
|
{
|
||||||
TreeNode<T>* sibling = (*parent)[i];
|
TreeNode<T, nullClears>* sibling = (*parent)[i];
|
||||||
if (sibling && sibling != this) // Skip NULLs
|
if (sibling && sibling != this) // Skip NULLs
|
||||||
siblings.push_back(sibling);
|
siblings.push_back_unique(sibling);
|
||||||
}
|
}
|
||||||
return siblings;
|
return siblings;
|
||||||
}
|
}
|
||||||
|
|
@ -299,7 +619,7 @@ public:
|
||||||
inline U32 getDepth() const
|
inline U32 getDepth() const
|
||||||
{
|
{
|
||||||
U32 depth = 0;
|
U32 depth = 0;
|
||||||
TreeNode<T>* current = parent;
|
TreeNode<T, nullClears>* current = parent;
|
||||||
while (current)
|
while (current)
|
||||||
{
|
{
|
||||||
depth++;
|
depth++;
|
||||||
|
|
@ -308,21 +628,124 @@ public:
|
||||||
return depth;
|
return depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline TreeNode<T>* getRoot()
|
inline TreeNode<T, nullClears>* getRoot()
|
||||||
{
|
{
|
||||||
TreeNode<T>* current = this;
|
TreeNode<T, nullClears>* current = this;
|
||||||
while (current->parent)
|
while (current->parent)
|
||||||
current = current->parent;
|
current = current->parent;
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const TreeNode<T>* getRoot() const
|
inline const TreeNode<T, nullClears>* getRoot() const
|
||||||
{
|
{
|
||||||
const TreeNode<T>* current = this;
|
const TreeNode<T, nullClears>* current = this;
|
||||||
while (current->parent)
|
while (current->parent)
|
||||||
current = current->parent;
|
current = current->parent;
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this node is present in the given vector (used for cycle detection).
|
||||||
|
bool isVisited(const Vector<TreeNode<T, nullClears>*>& 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<T, nullClears>* parent, Vector<TreeNode<T, nullClears>*>& outNodes, U32 maxCandidates = 8)
|
||||||
|
{
|
||||||
|
outNodes.clear();
|
||||||
|
for (U32 i = 0; i < parent->size() && outNodes.size() < maxCandidates; ++i) {
|
||||||
|
TreeNode<T, nullClears>* 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<T, nullClears>* 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<T, nullClears>*, const TreeNode<T, nullClears>*)
|
||||||
|
static bool findBestRotationPairing(
|
||||||
|
const Vector<TreeNode<T, nullClears>*>& nodes,
|
||||||
|
U32 groupSize,
|
||||||
|
F32(*costFunc)(const TreeNode<T, nullClears>*, const TreeNode<T, nullClears>*),
|
||||||
|
Vector<U32>& outGroupA,
|
||||||
|
Vector<U32>& outGroupB,
|
||||||
|
F32& outBestCost)
|
||||||
|
{
|
||||||
|
const U32 n = nodes.size();
|
||||||
|
if (n < 2 * groupSize) return false;
|
||||||
|
|
||||||
|
outBestCost = F32_MAX;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
Vector<U32> indices;
|
||||||
|
indices.setSize(n);
|
||||||
|
for (U32 i = 0; i < n; ++i) indices[i] = i;
|
||||||
|
|
||||||
|
Vector<U32> 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<typename Derived>
|
||||||
|
Derived* getChildAs(U32 i) { return static_cast<Derived*>(getChild(i)); }
|
||||||
|
template<typename Derived>
|
||||||
|
const Derived* getChildAs(U32 i) const { return static_cast<const Derived*>(getChild(i)); }
|
||||||
|
|
||||||
|
template<typename Derived>
|
||||||
|
Vector<Derived*> getChildrenAs() const
|
||||||
|
{
|
||||||
|
Vector<Derived*> out;
|
||||||
|
for (U32 i = 0; i < this->size(); ++i)
|
||||||
|
if ((*this)[i])
|
||||||
|
out.push_back(static_cast<Derived*>((*this)[i]));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class TreeObject : public SimObject {
|
class TreeObject : public SimObject {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue