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..ed2b614cd 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,172 @@ 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); + 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() { + 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); + 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, (), , + "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..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 @@ -28,21 +28,26 @@ #ifndef _PLATFORM_H_ #include "platform/platform.h" #endif +#include +#include #include #include "console/engineTypes.h" #include "console/engineTypeInfo.h" - +#ifndef _MMATHFN_H_ +#include "math/mMathFn.h" +#endif //----------------------------------------------------------------------------- // Helper definitions for the vector class. /// 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 +58,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 mTCount = 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 (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) + dMemmove(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) + 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]); + 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()) { + dMemset(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) + 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]); + 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) { + dMemmove(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 +792,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 +833,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 +855,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 +882,7 @@ class Vector /// @{ U32 memSize() const; - T* address() const; + T* address() const; U32 setSize(U32); void increment(); void decrement(); @@ -162,528 +897,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::mTCount == 0) + { + return EngineFieldTable::Field{ + "elementCount", "", 1, + TYPE(((ThisType*)16)->mState.mElementCount), + static_cast(FIELDOFFSET(mState.mElementCount)) + }; + } + else + return { NULL }; //trash + }; + template + static EngineFieldTable::Field getArraySizeField() + { + typedef Vector ThisType; + + if constexpr (VectorTraits::mTCount == 0) + { + return EngineFieldTable::Field{ + "arraySize", "", 1, + TYPE(((ThisType*)16)->mState.mArraySize), + static_cast(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 +1561,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 > 0 ? COUNT : this->arraySize(), 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 +1958,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 +1970,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 +1989,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 +2002,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/core/util/treeObject.cpp b/Engine/source/core/util/treeObject.cpp new file mode 100644 index 000000000..3a1976388 --- /dev/null +++ b/Engine/source/core/util/treeObject.cpp @@ -0,0 +1,489 @@ +//----------------------------------------------------------------------------- +// 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 +// 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) + { + 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(); +} + +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) + return; + + if (node == mRoot) + { + mRoot = NULL; + } + + _deleteNode(node, true); +} + +void TreeObject::_deleteNode(Node* node, bool unlinkFromMap) +{ + if (!node) + return; + + // Collect all keys in subtree for map cleanup + Vector keysToRemove; + if (unlinkFromMap) + { + node->forEachInSubtree([&keysToRemove](TreeNode* n) { + Node* scriptNode = static_cast(n); + keysToRemove.push_back(scriptNode->key); + }); + } + + + // 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; + + // Clean up map entries + if (unlinkFromMap) + { + for (U32 i = 0; i < keysToRemove.size(); i++) + { + mKeyMap.erase(keysToRemove[i]); + } + } +} + +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->getParent())->key; +} + +bool TreeObject::toParent(S32 key, S32 newParentKey) +{ + Node* targetNode = findNode(key); + Node* newParentNode = findNode(newParentKey); + + if (!targetNode || !newParentNode || key == newParentKey) + return false; + + // Check for circular reference (prevent making ancestor a child) + Node* checkNode = newParentNode; + while (checkNode != NULL) + { + if (checkNode->key == key) + return false; + checkNode = static_cast(checkNode->parent); + } + + // Remove from old parent and add to new parent + targetNode->nullParent(); + 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->getChildren(); + 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->getSiblings(); + 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); + mRoot = NULL; + } + + 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..6c2129b8a --- /dev/null +++ b/Engine/source/core/util/treeObject.h @@ -0,0 +1,1219 @@ +//----------------------------------------------------------------------------- +// 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 +// 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 _TREEOBJECT_H_ +#define _TREEOBJECT_H_ +#include "core/util/tVector.h" +#include "console/simObject.h" +#include "core/util/tDictionary.h" +#include "console/consoleTypes.h" + +// 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; + 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(), NodePtr p = NULL) + : data(val), parent(p) + { + _initChildrenStorage(); + } + + template + inline TreeNode(const Vector& children, NodePtr p = NULL) + : data(T()), parent(p) + { + _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; + } + } + } + } + + virtual ~TreeNode() + { + deleteChildren(); + parent = NULL; + } + + //------------------------------------------------------------------------- + // Deep Copy / Cloning + //------------------------------------------------------------------------- + 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()); + return copy; + } + + template + inline NodePtr cloneIf(Func pred) const + { + if (!pred(this)) return NULL; + auto* newNode = new Node(data, NULL); + for (U32 i = 0; i < this->size(); ++i) + if ((*this)[i]) + { + auto* filteredChild = (*this)[i]->cloneIf(pred); + if (filteredChild) + newNode->addChild(filteredChild); + } + return newNode; + } + + 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)) + { + if (!(*this)[i]->equals(other->getChild(i))) + return false; + } + else if ((*this)[i] || other->getChild(i)) + { + return false; + } + } + return true; + } + + static inline NodePtr mergeAt(NodePtr atNode, NodePtr subtree) + { + if (!atNode || !subtree) + return NULL; + atNode->addChild(subtree); + return subtree; + } + + static inline NodePtr cloneAt(NodePtr atNode, const NodePtr subtree) + { + if (!atNode || !subtree) + return NULL; + NodePtr newClone = subtree->clone(); + atNode->addChild(newClone); + return newClone; + } + + inline bool isSubtreeOf(const NodePtr 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. + inline Node& operator=(const Node& other) + { + if (this == &other) + return *this; + data = other.data; + parent = other.parent; + return *this; + } + + //------------------------------------------------------------------------- + // Node Type & Relationship Queries + //------------------------------------------------------------------------- + inline bool isRoot() const { return parent == NULL; } + + inline bool isLeaf() const + { + if constexpr (nullClears) + return this->size() == 0; + else + { + for (U32 i = 0; i < this->size(); ++i) + if ((*this)[i] != NULL) + return false; + return true; + } + } + + inline bool hasChildren() const + { + 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 bool hasChild(NodePtr child) const + { + return _containsChildPtr(child); + } + + 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++) + { + NodePtr child = (*this)[i]; + if (child && child->parent != this) + return false; + } + return true; + } + + 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 NodePtr findLeafIf(Func pred) + { + if (isLeaf() && pred(this)) return this; + for (U32 i = 0; i < this->size(); ++i) + if ((*this)[i]) + { + auto* found = (*this)[i]->findLeafIf(pred); + if (found) return found; + } + return NULL; + } + + //------------------------------------------------------------------------- + // Parent/Ancestor Logic + //------------------------------------------------------------------------- + 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 NodePtr getRoot() const + { + const NodePtr current = const_cast(this); + NodePtr c = current; + while (c->parent) + c = c->parent; + return c; + } + + 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 NodePtr other) const + { + return other ? other->isAncestorOf(const_cast(this)) : false; + } + + inline NodePtr lowestCommonAncestor(NodePtr other) + { + Vector pathA, pathB; + getPathToRoot(pathA); + other->getPathToRoot(pathB); + + NodePtr 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; + } + + inline void getPathToRoot(Vector& out) const + { + NodePtr node = const_cast(this); + while (node) + { + out.push_back(node); + node = node->parent; + } + } + + inline void setParent(NodePtr p) + { + if (parent == p) return; + if (parent) parent->removeChild(this); + parent = p; + if (parent) + parent->_insertChildPtrUnique(this); + } + + inline void nullParent(bool orphan = true) + { + if (orphan && parent) + parent->removeChild(this); + parent = NULL; + } + + 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) + { + node->parent->removeChild(node); + break; + } + } + } + node->parent = NULL; + } + + 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 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::Iterator iter = ancestorSet.begin(); iter != ancestorSet.end(); ++iter) + outAncestors.push_back(iter->key); + + 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 + { + U32 depth = 0; + NodePtr current = parent; + while (current) + { + depth++; + current = current->parent; + } + return depth; + } + + template + inline void forEachAncestor(Func callback) const + { + const Node* node = parent; + while (node) + { + callback(node); + node = node->parent; + } + } + + //------------------------------------------------------------------------- + // Child Logic + //------------------------------------------------------------------------- + inline NodePtr addChild(const T& val) + { + NodePtr child = new Node(val, this); + _insertChildPtrUnique(child); + return 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 + 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) + child->parent->removeChild(child); + + child->parent = this; + _insertChildPtrUnique(child); + } + + inline void setChild(U32 i, NodePtr child) + { + if constexpr (mTFixedChildren) + { + AssertFatal(i < CHILD_COUNT, "TreeNode::setChild - Index out of bounds for fixed child TreeNode"); + } + 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 constexpr (nullClears) + this->erase(i); + else + (*this)[i] = NULL; + return; + } + +#ifdef TORQUE_DEBUG + 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) + { + child->parent->removeChild(child); + } + 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 < (U32)this->size(); j++) + AssertFatal(j == i || (*this)[j] != child, "TreeNode::setChild - Attempted to set duplicate child"); + + (*this)[i] = child; + child->parent = this; + } + + inline NodePtr getChild(U32 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) + { + if (!children) return; + for (U32 i = 0; i < (U32)children->size(); i++) + { + NodePtr child = (*children)[i]; + if (!child) + continue; + + if (_containsChildPtr(child)) + continue; + +#ifdef TORQUE_DEBUG + 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) + { + bool foundInParent = false; + for (U32 k = 0; k < (U32)child->parent->size(); ++k) + { + if ((*child->parent)[k] == child) + { + foundInParent = true; + break; + } + } + if (foundInParent) + child->parent->removeChild(child); + else + child->parent = NULL; + } + + if (child->parent == this) + continue; + + child->parent = this; + _insertChildPtrUnique(child); + } + } + + 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 + { + child->parent = NULL; + } + } + } + + _clearAllChildSlots(); + } + + 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 + { + child->parent = NULL; + } + } + children.push_back_unique(child); + } + + _clearAllChildSlots(); + + for (U32 i = 0; i < (U32)children.size(); i++) + { + if (children[i]) + delete children[i]; + } + } + + inline void removeChild(NodePtr child) + { + AssertFatal(child != NULL, "TreeNode::removeChild - Attempted to remove NULL child"); + bool found = false; + + for (U32 i = 0; i < (U32)this->size(); i++) + { + if ((*this)[i] == child) + { + child->parent = NULL; + if constexpr (nullClears) + this->erase(i); + else + (*this)[i] = NULL; + found = true; + break; + } + } + + if (!found) + { +#ifdef TORQUE_DEBUG + 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); + 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) + { + 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(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 >= (U32)this->size()) + return; + NodePtr child = (*this)[i]; + if (child && child->parent == this) + child->parent = NULL; + (*this)[i] = NULL; + } + + inline void swapChildren(U32 i, U32 j) + { + if (i >= (U32)this->size() || j >= (U32)this->size() || i == j) + return; + + 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; + } + + //------------------------------------------------------------------------- + // Sibling Logic + //------------------------------------------------------------------------- + inline Vector getSiblings() const + { + Vector siblings; + if (!parent) return siblings; + siblings.reserve(parent->size() - 1); + for (U32 i = 0; i < (U32)parent->size(); i++) + { + NodePtr sibling = (*parent)[i]; + if (sibling && sibling != this) + siblings.push_back_unique(sibling); + } + return siblings; + } + + //------------------------------------------------------------------------- + // Traversal & Collection + //------------------------------------------------------------------------- + inline void findLeaves(Vector& out) + { + if (isLeaf()) + out.push_back_unique(this); + else + for (U32 i = 0; i < (U32)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 < (U32)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 < (U32)this->size(); ++i) + if ((*this)[i]) + (*this)[i]->collectSubtreeData(dataList); + } + + template + inline void forEachChild(Func callback) + { + for (U32 i = 0; i < (U32)this->size(); i++) + if ((*this)[i]) + callback((*this)[i]); + } + + template + inline void forEachChild(Func callback) const + { + for (U32 i = 0; i < (U32)this->size(); i++) + if ((*this)[i]) + callback((*this)[i]); + } + + template + inline void forEachInSubtree(Func callback) + { + callback(this); + for (U32 i = 0; i < (U32)this->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 < (U32)this->size(); i++) + if ((*this)[i]) + (*this)[i]->forEachLeaf(callback); + } + + template + inline void traverseBreadthFirst(Func callback) + { + Vector queue; + queue.push_back(this); + while (!queue.empty()) + { + NodePtr node = queue.first(); + queue.erase(0); + callback(node); + for (U32 i = 0; i < (U32)node->size(); ++i) + if ((*node)[i]) + queue.push_back((*node)[i]); + } + } + + template + inline void refitPathToRoot(Func refitFunc) + { + NodePtr node = parent; + while (node) + { + refitFunc(node); + node = node->parent; + } + } + + template + inline NodePtr findIf(Func pred) + { + if (pred(this)) return this; + for (U32 i = 0; i < (U32)this->size(); ++i) + if ((*this)[i]) + { + auto* found = (*this)[i]->findIf(pred); + if (found) return found; + } + return NULL; + } + + template + inline void findAllIf(Func pred, Vector& out) + { + if (pred(this)) + out.push_back(this); + for (U32 i = 0; i < (U32)this->size(); ++i) + if ((*this)[i]) + (*this)[i]->findAllIf(pred, out); + } + + template + inline U32 countIf(Func pred) const + { + U32 count = pred(this) ? 1 : 0; + for (U32 i = 0; i < (U32)this->size(); ++i) + if ((*this)[i]) + count += (*this)[i]->countIf(pred); + return count; + } + + template + 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]) + { + (*this)[i]->removeIf(pred); + } + } + } + + //------------------------------------------------------------------------- + // Tree Metrics + //------------------------------------------------------------------------- + inline U32 getTreeSize() const + { + U32 count = 1; + for (U32 i = 0; i < (U32)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 < (U32)this->size(); i++) + if ((*this)[i]) + { + U32 childDepth = (*this)[i]->getMaxDepth(); + if (childDepth > maxDepth) + maxDepth = childDepth; + } + return maxDepth + 1; + } + + //------------------------------------------------------------------------- + // Rotation/Balance Utilities + //------------------------------------------------------------------------- + static inline U32 gatherRotationCandidates(NodePtr parent, Vector& outNodes, U32 maxCandidates = 8) + { + outNodes.clear(); + for (U32 i = 0; i < (U32)parent->size() && outNodes.size() < maxCandidates; ++i) + { + NodePtr child = (*parent)[i]; + if (!child) continue; + if (child->isLeaf()) + { + outNodes.push_back_unique(child); + } + 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, + U32 groupSize, + F32(*costFunc)(const Node*, const Node*), + 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; + } + + //------------------------------------------------------------------------- + // 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 + 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() + { + Vector out; + 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 + { + Vector out; + 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) + { + if (isLeaf()) + callback(static_cast(this)); + else + for (U32 i = 0; i < (U32)this->size(); 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 < (U32)this->size(); i++) + if ((*this)[i]) + (*this)[i]->template forEachLeafAs(callback); + } +}; + +class TreeObject : public SimObject { + typedef SimObject Parent; + +public: + struct Node : public TreeNode { + S32 key; + ConsoleBaseType* type; + Node(S32 k, ConsoleBaseType* t) : TreeNode(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); +}; +#endif //_TREEOBJECT_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 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 0d377f23f..dc2c32599 100644 --- a/Engine/source/platform/platform.h +++ b/Engine/source/platform/platform.h @@ -112,7 +112,7 @@ enum DriveType // Some forward declares for later. class Point2I; -template class Vector; + template class Signal; struct InputEventInfo; @@ -245,10 +245,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); @@ -285,8 +285,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/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