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:
AzaezelX 2026-01-30 00:52:33 -06:00
parent 09f0fa2861
commit 8ec65267ea
2 changed files with 622 additions and 191 deletions

View file

@ -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<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();
}
@ -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*>(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)
{
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<S32> keysToRemove;
if (unlinkFromMap)
{
Node* child = static_cast<Node*>((*node)[i]);
_deleteNode(child, unlinkFromMap);
node->forEachInSubtree([&keysToRemove](TreeNode<void*>* n) {
Node* scriptNode = static_cast<Node*>(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<Node*>(checkNode->parent);
}
if (targetNode->parent)
{
TreeNode<void*>* 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);

View file

@ -27,239 +27,533 @@
#include "core/util/tDictionary.h"
#include "console/consoleTypes.h"
template <class T>
class TreeNode : public Vector<TreeNode<T>*> {
// Add nullClears as a template argument (default: true. set false for BVH/quad/octrees)
template <class T, bool nullClears = true>
class TreeNode : public Vector<TreeNode<T, nullClears>*> {
public:
T data;
TreeNode<T>* parent;
TreeNode(const T& val = T(), TreeNode<T>* p = NULL) : data(val), parent(p) {}
TreeNode<T, nullClears>* parent;
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()
{
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<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) parent->removeChild(this);
parent = p;
if (parent) parent->push_back(this);
if (parent) parent->push_back_unique(this);
}
//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);
push_back(child);
TreeNode<T, nullClears>* child = new TreeNode<T, nullClears>(val, this);
this->push_back_unique(child);
return child;
}
inline void addChild(TreeNode<T>* 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<T, nullClears>* 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<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;
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();
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<T>* oldChild = (*this)[i];
TreeNode<T, nullClears>* 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<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 NULL;
}
inline void addChildren(const Vector<T>* children)
inline void addChildren(const Vector<TreeNode<T, nullClears>*>* children)
{
if (!children) return;
for (U32 i = 0; i < children->size(); i++)
{
TreeNode<T>* child = new TreeNode<T>((*children)[i], this);
push_back(child);
TreeNode<T, nullClears>* 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<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)
{
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<T>* child = (*this)[i];
if (child)
child->parent = NULL;
erase(i);
}
AssertFatal(i < this->size(), "TreeNode::removeChild - Index out of bounds");
TreeNode<T, nullClears>* child = (*this)[i];
if (child)
child->parent = NULL;
if constexpr (nullClears)
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++)
{
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<T, nullClears>* 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<T>* other) { clear(); addChildren(other); }
inline U32 getNumChildren() const { return size(); }
inline bool hasChildren() const { return size() > 0; }
inline Vector<TreeNode<T>*> getChildren() const
inline bool hasChildren() const
{
Vector<TreeNode<T>*> 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<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;
}
inline void deleteChildren()
{
Vector<TreeNode<T>*> children;
for (U32 i = 0; i < size(); i++)
Vector<TreeNode<T, nullClears>*> children;
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();
@ -267,24 +561,50 @@ public:
{
if (children[i])
{
children[i]->parent = NULL;
// Child should already be detached
delete children[i];
}
}
}
//sibling logic
inline Vector<TreeNode<T>*> getSiblings() const
inline void collectSubtree(Vector<TreeNode<T, nullClears>*>& nodes)
{
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;
siblings.reserve(parent->size() - 1);
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
siblings.push_back(sibling);
siblings.push_back_unique(sibling);
}
return siblings;
}
@ -299,7 +619,7 @@ public:
inline U32 getDepth() const
{
U32 depth = 0;
TreeNode<T>* current = parent;
TreeNode<T, nullClears>* current = parent;
while (current)
{
depth++;
@ -308,21 +628,124 @@ public:
return depth;
}
inline TreeNode<T>* getRoot()
inline TreeNode<T, nullClears>* getRoot()
{
TreeNode<T>* current = this;
TreeNode<T, nullClears>* current = this;
while (current->parent)
current = current->parent;
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)
current = current->parent;
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 {