From 8705b57f71756557f63957d0d095011f2c347a1b Mon Sep 17 00:00:00 2001 From: Ken Pruiksma Date: Fri, 19 Nov 2021 12:08:35 -0600 Subject: [PATCH] Adding unit tests for IndexedDataVector (#5724) * Rename IndexedDataVectorTests -> MultiIndexedDataVectorTests Signed-off-by: Ken Pruiksma * Adding unit tests for IndexedDataVector. Updated fixture for MultiIndexedDataVector. Signed-off-by: Ken Pruiksma * Fixed a bug in the test. Updated IndexedDataVector with more/better comments and removed a non-const function that wasn't necessary. Signed-off-by: Ken Pruiksma * Further updates to some comments Signed-off-by: Ken Pruiksma --- .../Atom/Feature/Utils/IndexedDataVector.h | 29 +- .../Atom/Feature/Utils/IndexedDataVector.inl | 8 +- .../Feature/Utils/MultiIndexedDataVector.h | 2 +- .../Code/Tests/IndexedDataVectorTests.cpp | 428 +++++++----------- .../Tests/MultiIndexedDataVectorTests.cpp | 320 +++++++++++++ .../atom_feature_common_tests_files.cmake | 1 + 6 files changed, 501 insertions(+), 287 deletions(-) create mode 100644 Gems/Atom/Feature/Common/Code/Tests/MultiIndexedDataVectorTests.cpp diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/IndexedDataVector.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/IndexedDataVector.h index 82cc1e7d50..b2d483e48c 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/IndexedDataVector.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/IndexedDataVector.h @@ -27,31 +27,50 @@ namespace AZ::Render static constexpr IndexType NoFreeSlot = std::numeric_limits::max(); IndexType m_firstFreeSlot = NoFreeSlot; + //! Clears all data and resets to initial state. void Clear(); + + //! Creates a new entry, default-constructs it, and returns an index that references it. IndexType GetFreeSlotIndex(); + + //! Destroys the data referenced by index and frees that index for future use. void RemoveIndex(IndexType index); + + //! Destroys the data and related index by using a pointer to the data itself. void RemoveData(DataType* data); + //! Returns a reference to the data using the provided index. DataType& GetData(IndexType index); const DataType& GetData(IndexType index) const; + + //! Returns a count of how many items are stored in the IndexedDataVector size_t GetDataCount() const; + //! Returns a reference to the internal data vector. + //! This vector should not be altered by calling code or the IndexedDataVector will be corrupted AZStd::vector& GetDataVector(); const AZStd::vector& GetDataVector() const; + + //! Returns a reference to the internal vector. + const AZStd::vector& GetDataToIndexVector() const; - AZStd::vector& GetIndexVector(); - const AZStd::vector& GetIndexVector() const; - + //! Returns the offset into the internal data vector for a given index. IndexType GetRawIndex(IndexType index) const; + + //! Returns the logical index for data given its pointer, which could passed to + //! GetData() to retrieve the data again. IndexType GetIndexForData(const DataType* data) const; private: constexpr static size_t InitialReservedSize = 128; - // Stores data indices and an embedded free list + // Indices to data and an embedded free list in the unused entries AZStd::vector m_indices; - // Stores the indirection index + + // Map of the physical index in m_data to the logical index for that data in m_indices. AZStd::vector m_dataToIndices; + + // The actual data. AZStd::vector m_data; }; } // namespace AZ::Render diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/IndexedDataVector.inl b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/IndexedDataVector.inl index 581186dbcc..03c3564ce9 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/IndexedDataVector.inl +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/IndexedDataVector.inl @@ -125,13 +125,7 @@ namespace AZ::Render } template - inline AZStd::vector& IndexedDataVector::GetIndexVector() - { - return m_dataToIndices; - } - - template - inline const AZStd::vector& IndexedDataVector::GetIndexVector() const + inline const AZStd::vector& IndexedDataVector::GetDataToIndexVector() const { return m_dataToIndices; } diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/MultiIndexedDataVector.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/MultiIndexedDataVector.h index e815d85cf3..c0f11dd159 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/MultiIndexedDataVector.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/MultiIndexedDataVector.h @@ -17,7 +17,7 @@ namespace AZ { //! MultiIndexedDataVector is similar to IndexedDataVector but adds support for multiple different data vectors each containing different types //! i.e. structure of (N) arrays - //! See IndexedDataVectorTests.cpp for examples of use + //! See MultiIndexedDataVectorTests.cpp for examples of use template class MultiIndexedDataVector { diff --git a/Gems/Atom/Feature/Common/Code/Tests/IndexedDataVectorTests.cpp b/Gems/Atom/Feature/Common/Code/Tests/IndexedDataVectorTests.cpp index a9cb6e2621..88e67538c5 100644 --- a/Gems/Atom/Feature/Common/Code/Tests/IndexedDataVectorTests.cpp +++ b/Gems/Atom/Feature/Common/Code/Tests/IndexedDataVectorTests.cpp @@ -8,338 +8,218 @@ #include #include -#include +#include #include #include - namespace UnitTest { using namespace AZ; using namespace AZ::Render; - + class IndexedDataVectorTests - : public ::testing::Test + : public UnitTest::AllocatorsTestFixture { public: void SetUp() override { - CreateAllocator(); + UnitTest::AllocatorsTestFixture::SetUp(); } void TearDown() override { - DestroyAllocator(); + UnitTest::AllocatorsTestFixture::TearDown(); } - - private: - void CreateAllocator() + template + IndexedDataVector SetupIndexedDataVector(size_t size, T initialValue = T(0), T incrementAmount = T(1), AZStd::vector* indices = nullptr) { - static constexpr size_t NumMBToAllocate = 1; - SystemAllocator::Descriptor desc; - desc.m_heap.m_numFixedMemoryBlocks = 1; - desc.m_heap.m_fixedMemoryBlocksByteSize[0] = NumMBToAllocate * 1024 * 1024; - m_memBlock = AZ_OS_MALLOC( - desc.m_heap.m_fixedMemoryBlocksByteSize[0], - desc.m_heap.m_memoryBlockAlignment); - desc.m_heap.m_fixedMemoryBlocks[0] = m_memBlock; - - AllocatorInstance::Create(desc); + IndexedDataVector data; + T value = initialValue; + for (size_t i = 0; i < size; ++i) + { + uint16_t index = data.GetFreeSlotIndex(); + EXPECT_NE(index, IndexedDataVector::NoFreeSlot); + if (indices) + { + indices->push_back(index); + } + if (index != IndexedDataVector::NoFreeSlot) + { + data.GetData(index) = value; + value += incrementAmount; + } + } + return data; } - void DestroyAllocator() + template + void ShuffleIndexedDataVector(IndexedDataVector& dataVector, AZStd::vector& indices) { - AllocatorInstance::Destroy(); - AZ_OS_FREE(m_memBlock); - m_memBlock = nullptr; + AZStd::vector values; + + // remove every other element and store it + for (size_t i = 0; i < indices.size(); ++i) + { + values.push_back(dataVector.GetData(indices.at(i))); + dataVector.RemoveIndex(indices.at(i)); + indices.erase(&indices.at(i)); + } + + for (T value : values) + { + uint16_t index = dataVector.GetFreeSlotIndex(); + indices.push_back(index); + dataVector.GetData(index) = value; + } } - void* m_memBlock = nullptr; }; - - TEST_F(IndexedDataVectorTests, TestInsert) + + TEST_F(IndexedDataVectorTests, Construction) { - enum Types - { - IntType = 0, - DoubleType = 1, - }; - - MultiIndexedDataVector myVec; - constexpr int NumToInsert = 5; + IndexedDataVector testVector; + uint16_t index = testVector.GetFreeSlotIndex(); + EXPECT_NE(index, IndexedDataVector::NoFreeSlot); + } + + TEST_F(IndexedDataVectorTests, TestInsertGetBasic) + { + constexpr size_t count = 16; + constexpr int initialValue = 0; + constexpr int increment = 1; AZStd::vector indices; - - for (int i = 0; i < NumToInsert; ++i) + IndexedDataVector testVector = SetupIndexedDataVector(count, initialValue, increment, &indices); + + int value = initialValue; + for (size_t i = 0; i < count; ++i) { - auto index = myVec.GetFreeSlotIndex(); - indices.push_back(index); - myVec.GetData(index) = i; - myVec.GetData(index) = (double)i; + EXPECT_EQ(testVector.GetData(indices.at(i)), value); + value += increment; + } + } + + TEST_F(IndexedDataVectorTests, TestInsertGetComplex) + { + constexpr size_t count = 16; + constexpr int initialValue = 0; + constexpr int increment = 1; + + AZStd::vector indices; + IndexedDataVector testVector = SetupIndexedDataVector(count, initialValue, increment, &indices); + + // Create a set of the data that should be in the IndexedDataVector + AZStd::set values; + for (int i = 0; i < count; ++i) + { + values.emplace(initialValue + i * increment); } - for (size_t i = 0; i < NumToInsert; ++i) + // Add and remove items to shuffle the underlying data + ShuffleIndexedDataVector(testVector, indices); + + // Check to make sure all the data is still there + AZStd::vector& underlyingVector = testVector.GetDataVector(); + for (size_t i = 0; i < underlyingVector.size(); ++i) { - auto index = indices[i]; - EXPECT_EQ(i, myVec.GetData(index)); - EXPECT_EQ((double)i, myVec.GetData(index)); + EXPECT_TRUE(values.contains(underlyingVector.at(i))); } } TEST_F(IndexedDataVectorTests, TestSize) { - enum Types - { - IntType = 0, - }; + constexpr size_t count = 32; - MultiIndexedDataVector myVec; - constexpr int NumToInsert = 5; - for (int i = 0; i < NumToInsert; ++i) - { - auto index = myVec.GetFreeSlotIndex(); - myVec.GetData(index) = i; - } - EXPECT_EQ(NumToInsert, myVec.GetDataCount()); - EXPECT_EQ(NumToInsert, myVec.GetDataVector().size()); - - myVec.Clear(); - - EXPECT_EQ(0, myVec.GetDataCount()); - EXPECT_EQ(0, myVec.GetDataVector().size()); + IndexedDataVector testVector = SetupIndexedDataVector(count); + EXPECT_EQ(testVector.GetDataCount(), count); } - TEST_F(IndexedDataVectorTests, TestErase) + TEST_F(IndexedDataVectorTests, TestClear) { - enum Types - { - IntType = 0, - }; - - MultiIndexedDataVector myVec; - constexpr int NumToInsert = 200; - AZStd::unordered_map valueToIndex; - - for (int i = 0; i < NumToInsert; ++i) - { - auto index = myVec.GetFreeSlotIndex(); - valueToIndex[i] = index; - myVec.GetData(index) = i; - } - - // erase every even number - for (int i = 0; i < NumToInsert; i += 2) - { - uint16_t index = valueToIndex[i]; - auto previousRawIndex = myVec.GetRawIndex(index); - auto movedIndex = myVec.RemoveIndex(index); - if (movedIndex != MultiIndexedDataVector::NoFreeSlot) - { - auto newRawIndex = myVec.GetRawIndex(movedIndex); - - // RemoveIndex() returns the index of the item that moves into its spot if any, so check - // to make sure the Raw index of the old matches the raw index of the new - EXPECT_EQ(previousRawIndex, newRawIndex); - } - valueToIndex.erase(i); - } - - for (const auto& iter : valueToIndex) - { - int val = iter.first; - uint16_t index = iter.second; - EXPECT_EQ(val, myVec.GetData(index)); - } + constexpr size_t count = 32; + IndexedDataVector testVector = SetupIndexedDataVector(count); + testVector.Clear(); + EXPECT_EQ(testVector.GetDataCount(), 0); } - TEST_F(IndexedDataVectorTests, TestManyTypes) + TEST_F(IndexedDataVectorTests, TestRemove) { - enum Types + constexpr size_t count = 8; + constexpr int initialValue = 0; + constexpr int increment = 8; + + AZStd::vector indices; + IndexedDataVector testVector = SetupIndexedDataVector(count, initialValue, increment, &indices); + + // Remove every other element by index + for (uint16_t i = 0; i < count; i += 2) { - IntType = 0, - StringType = 1, - DoubleType = 2, - FloatType = 3, - CharType = 4, - }; - - MultiIndexedDataVector myVec; - auto index = myVec.GetFreeSlotIndex(); - - constexpr int TestIntVal = INT_MIN; - constexpr double TestDoubleVal = -DBL_MIN; - const AZStd::string TestStringVal = "This is an AZStd::string."; - constexpr float TestFloatVal = FLT_MAX; - const char* TestConstPointerVal = "This is a C array."; - - myVec.GetData(index) = TestIntVal; - myVec.GetData(index) = TestStringVal; - myVec.GetData(index) = TestDoubleVal; - myVec.GetData(index) = TestFloatVal; - myVec.GetData(index) = TestConstPointerVal; - - EXPECT_EQ(TestIntVal, static_cast(myVec.GetData(index))); - EXPECT_EQ(TestStringVal, static_cast(myVec.GetData(index))); - EXPECT_EQ(TestDoubleVal, static_cast(myVec.GetData(index))); - EXPECT_EQ(TestFloatVal, static_cast(myVec.GetData(index))); - EXPECT_STREQ(TestConstPointerVal, static_cast(myVec.GetData(index))); - } - - MultiIndexedDataVector CreateTestVector(AZStd::vector& indices) - { - enum Types - { - IntType = 0, - FloatType = 1, - }; - - MultiIndexedDataVector myVec; - constexpr int32_t Count = 10; - int32_t startInt = 10; - float startFloat = 2.0f; + testVector.RemoveIndex(i); + } - // Create some initial values - for (uint32_t i = 0; i < Count; ++i) + EXPECT_EQ(testVector.GetDataCount(), count / 2); + + // Make sure the rest of the data is still there + AZStd::vector remainingIndices; + for (size_t i = 1; i < count; i += 2) { - uint16_t index = myVec.GetFreeSlotIndex(); - indices.push_back(index); - myVec.GetData(index) = startInt; - myVec.GetData(index) = startFloat; - startInt += 1; - startFloat += 1.0f; + int value = testVector.GetData(indices.at(i)); + EXPECT_EQ(value, initialValue + increment * i); + remainingIndices.push_back(indices.at(i)); } - return myVec; + // remove the rest of the valus by value + for (uint16_t index : remainingIndices) + { + int* valuePtr = &testVector.GetData(index); + testVector.RemoveData(valuePtr); + } + + EXPECT_EQ(testVector.GetDataCount(), 0); + } + + TEST_F(IndexedDataVectorTests, TestIndexForData) + { + constexpr size_t count = 8; + constexpr int initialValue = 0; + constexpr int increment = 8; + + AZStd::vector indices; + IndexedDataVector testVector = SetupIndexedDataVector(count, initialValue, increment, &indices); + + // Add and remove items to shuffle the underlying data + ShuffleIndexedDataVector(testVector, indices); + + AZStd::vector& underlyingVector = testVector.GetDataVector(); + for (size_t i = 0; i < underlyingVector.size(); ++i) + { + int value = underlyingVector.at(i); + uint16_t index = testVector.GetIndexForData(&underlyingVector.at(i)); + + // The data from GetData(index) should match for the index retrieved using GetIndexForData() for the same data. + EXPECT_EQ(testVector.GetData(index), value); + } } - void CheckIndexedData(MultiIndexedDataVector& data, AZStd::vector& indices) + TEST_F(IndexedDataVectorTests, TestRawIndex) { - enum Types - { - IntType = 0, - FloatType = 1, - }; - - // For each index, get its data and make sure GetIndexForData returns the same - // index used to retrieve the data - for (uint32_t i = 0; i < data.GetDataCount(); ++i) - { - int32_t& intData = data.GetData(indices.at(i)); - uint16_t indexForData = data.GetIndexForData(&intData); - EXPECT_EQ(indices.at(i), indexForData); - - float& floatData = data.GetData(indices.at(i)); - indexForData = data.GetIndexForData(&floatData); - EXPECT_EQ(indices.at(i), indexForData); - } - } - - TEST_F(IndexedDataVectorTests, GetIndexForDataSimple) - { - AZStd::vector indices; - MultiIndexedDataVector myVec = CreateTestVector(indices); - CheckIndexedData(myVec, indices); - } - - TEST_F(IndexedDataVectorTests, GetIndexForDataComplex) - { - enum Types - { - IntType = 0, - FloatType = 1, - }; + constexpr size_t count = 8; + constexpr int initialValue = 0; + constexpr int increment = 8; AZStd::vector indices; - MultiIndexedDataVector myVec = CreateTestVector(indices); + IndexedDataVector testVector = SetupIndexedDataVector(count, initialValue, increment, &indices); - // remove every other value to shuffle the data around - for (uint32_t i = 0; i < myVec.GetDataCount(); i += 2) - { - myVec.RemoveIndex(indices.at(i)); - } + // Add and remove items to shuffle the underlying data + ShuffleIndexedDataVector(testVector, indices); - int32_t startInt = 100; - float startFloat = 20.0f; - - // Add some data back in - const size_t count = myVec.GetDataCount(); - for (uint32_t i = 0; i < count; i += 2) + AZStd::vector& underlyingVector = testVector.GetDataVector(); + for (size_t i = 0; i < indices.size(); ++i) { - uint16_t index = myVec.GetFreeSlotIndex(); - indices.at(i) = index; - myVec.GetData(index) = startInt; - myVec.GetData(index) = startFloat; - startInt += 1; - startFloat += 1.0f; + // Check that the data retrieved from GetData for a given index matches the data in the underlying vector for the raw index. + EXPECT_EQ(testVector.GetData(indices.at(i)), underlyingVector.at(testVector.GetRawIndex(indices.at(i)))); } - CheckIndexedData(myVec, indices); - } - - TEST_F(IndexedDataVectorTests, ForEach) - { - enum Types - { - IntType = 0, - FloatType = 1, - }; - - MultiIndexedDataVector myVec; - constexpr int32_t Count = 10; - int32_t startInt = 10; - float startFloat = 2.0f; - - AZStd::vector indices; - AZStd::set intValues; - AZStd::set floatValues; - - // Create some initial values - for (uint32_t i = 0; i < Count; ++i) - { - uint16_t index = myVec.GetFreeSlotIndex(); - indices.push_back(index); - myVec.GetData(index) = startInt; - myVec.GetData(index) = startFloat; - intValues.insert(startInt); - floatValues.insert(startFloat); - startInt += 1; - startFloat += 1.0f; - } - - uint32_t visitCount = 0; - myVec.ForEach([&](int32_t value) -> bool - { - intValues.erase(value); - ++visitCount; - return true; // keep iterating - }); - - // All ints should have been visited and found in the set - EXPECT_EQ(visitCount, Count); - EXPECT_EQ(intValues.size(), 0); - - visitCount = 0; - myVec.ForEach([&](float value) -> bool - { - floatValues.erase(value); - ++visitCount; - return true; // keep iterating - }); - - // All floats should have been visited and found in the set - EXPECT_EQ(visitCount, Count); - EXPECT_EQ(floatValues.size(), 0); - - visitCount = 0; - myVec.ForEach([&]([[maybe_unused]] int32_t value) -> bool - { - ++visitCount; - return false; // stop iterating - }); - - // Since false is immediately returned, only one element should have been visited. - EXPECT_EQ(visitCount, 1); - } } diff --git a/Gems/Atom/Feature/Common/Code/Tests/MultiIndexedDataVectorTests.cpp b/Gems/Atom/Feature/Common/Code/Tests/MultiIndexedDataVectorTests.cpp new file mode 100644 index 0000000000..5a317c41aa --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Tests/MultiIndexedDataVectorTests.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include + + +namespace UnitTest +{ + using namespace AZ; + using namespace AZ::Render; + + class MultiIndexedDataVectorTests + : public UnitTest::AllocatorsTestFixture + { + public: + void SetUp() override + { + UnitTest::AllocatorsTestFixture::SetUp(); + } + + void TearDown() override + { + UnitTest::AllocatorsTestFixture::TearDown(); + } + }; + + TEST_F(MultiIndexedDataVectorTests, TestInsert) + { + enum Types + { + IntType = 0, + DoubleType = 1, + }; + + MultiIndexedDataVector myVec; + constexpr int NumToInsert = 5; + + AZStd::vector indices; + + for (int i = 0; i < NumToInsert; ++i) + { + auto index = myVec.GetFreeSlotIndex(); + indices.push_back(index); + myVec.GetData(index) = i; + myVec.GetData(index) = (double)i; + } + + for (size_t i = 0; i < NumToInsert; ++i) + { + auto index = indices[i]; + EXPECT_EQ(i, myVec.GetData(index)); + EXPECT_EQ((double)i, myVec.GetData(index)); + } + } + + TEST_F(MultiIndexedDataVectorTests, TestSize) + { + enum Types + { + IntType = 0, + }; + + MultiIndexedDataVector myVec; + constexpr int NumToInsert = 5; + for (int i = 0; i < NumToInsert; ++i) + { + auto index = myVec.GetFreeSlotIndex(); + myVec.GetData(index) = i; + } + EXPECT_EQ(NumToInsert, myVec.GetDataCount()); + EXPECT_EQ(NumToInsert, myVec.GetDataVector().size()); + + myVec.Clear(); + + EXPECT_EQ(0, myVec.GetDataCount()); + EXPECT_EQ(0, myVec.GetDataVector().size()); + } + + TEST_F(MultiIndexedDataVectorTests, TestErase) + { + enum Types + { + IntType = 0, + }; + + MultiIndexedDataVector myVec; + constexpr int NumToInsert = 200; + AZStd::unordered_map valueToIndex; + + for (int i = 0; i < NumToInsert; ++i) + { + auto index = myVec.GetFreeSlotIndex(); + valueToIndex[i] = index; + myVec.GetData(index) = i; + } + + // erase every even number + for (int i = 0; i < NumToInsert; i += 2) + { + uint16_t index = valueToIndex[i]; + auto previousRawIndex = myVec.GetRawIndex(index); + auto movedIndex = myVec.RemoveIndex(index); + if (movedIndex != MultiIndexedDataVector::NoFreeSlot) + { + auto newRawIndex = myVec.GetRawIndex(movedIndex); + + // RemoveIndex() returns the index of the item that moves into its spot if any, so check + // to make sure the Raw index of the old matches the raw index of the new + EXPECT_EQ(previousRawIndex, newRawIndex); + } + valueToIndex.erase(i); + } + + for (const auto& iter : valueToIndex) + { + int val = iter.first; + uint16_t index = iter.second; + EXPECT_EQ(val, myVec.GetData(index)); + } + } + + TEST_F(MultiIndexedDataVectorTests, TestManyTypes) + { + enum Types + { + IntType = 0, + StringType = 1, + DoubleType = 2, + FloatType = 3, + CharType = 4, + }; + + MultiIndexedDataVector myVec; + auto index = myVec.GetFreeSlotIndex(); + + constexpr int TestIntVal = INT_MIN; + constexpr double TestDoubleVal = -DBL_MIN; + const AZStd::string TestStringVal = "This is an AZStd::string."; + constexpr float TestFloatVal = FLT_MAX; + const char* TestConstPointerVal = "This is a C array."; + + myVec.GetData(index) = TestIntVal; + myVec.GetData(index) = TestStringVal; + myVec.GetData(index) = TestDoubleVal; + myVec.GetData(index) = TestFloatVal; + myVec.GetData(index) = TestConstPointerVal; + + EXPECT_EQ(TestIntVal, static_cast(myVec.GetData(index))); + EXPECT_EQ(TestStringVal, static_cast(myVec.GetData(index))); + EXPECT_EQ(TestDoubleVal, static_cast(myVec.GetData(index))); + EXPECT_EQ(TestFloatVal, static_cast(myVec.GetData(index))); + EXPECT_STREQ(TestConstPointerVal, static_cast(myVec.GetData(index))); + } + + MultiIndexedDataVector CreateTestVector(AZStd::vector& indices) + { + enum Types + { + IntType = 0, + FloatType = 1, + }; + + MultiIndexedDataVector myVec; + constexpr int32_t Count = 10; + int32_t startInt = 10; + float startFloat = 2.0f; + + // Create some initial values + for (uint32_t i = 0; i < Count; ++i) + { + uint16_t index = myVec.GetFreeSlotIndex(); + indices.push_back(index); + myVec.GetData(index) = startInt; + myVec.GetData(index) = startFloat; + startInt += 1; + startFloat += 1.0f; + } + + return myVec; + } + + void CheckIndexedData(MultiIndexedDataVector& data, AZStd::vector& indices) + { + enum Types + { + IntType = 0, + FloatType = 1, + }; + + // For each index, get its data and make sure GetIndexForData returns the same + // index used to retrieve the data + for (uint32_t i = 0; i < data.GetDataCount(); ++i) + { + int32_t& intData = data.GetData(indices.at(i)); + uint16_t indexForData = data.GetIndexForData(&intData); + EXPECT_EQ(indices.at(i), indexForData); + + float& floatData = data.GetData(indices.at(i)); + indexForData = data.GetIndexForData(&floatData); + EXPECT_EQ(indices.at(i), indexForData); + } + } + + TEST_F(MultiIndexedDataVectorTests, GetIndexForDataSimple) + { + AZStd::vector indices; + MultiIndexedDataVector myVec = CreateTestVector(indices); + CheckIndexedData(myVec, indices); + } + + TEST_F(MultiIndexedDataVectorTests, GetIndexForDataComplex) + { + enum Types + { + IntType = 0, + FloatType = 1, + }; + + AZStd::vector indices; + MultiIndexedDataVector myVec = CreateTestVector(indices); + + // remove every other value to shuffle the data around + for (uint32_t i = 0; i < myVec.GetDataCount(); i += 2) + { + myVec.RemoveIndex(indices.at(i)); + } + + int32_t startInt = 100; + float startFloat = 20.0f; + + // Add some data back in + const size_t count = myVec.GetDataCount(); + for (uint32_t i = 0; i < count; i += 2) + { + uint16_t index = myVec.GetFreeSlotIndex(); + indices.at(i) = index; + myVec.GetData(index) = startInt; + myVec.GetData(index) = startFloat; + startInt += 1; + startFloat += 1.0f; + } + + CheckIndexedData(myVec, indices); + } + + TEST_F(MultiIndexedDataVectorTests, ForEach) + { + enum Types + { + IntType = 0, + FloatType = 1, + }; + + MultiIndexedDataVector myVec; + constexpr int32_t Count = 10; + int32_t startInt = 10; + float startFloat = 2.0f; + + AZStd::vector indices; + AZStd::set intValues; + AZStd::set floatValues; + + // Create some initial values + for (uint32_t i = 0; i < Count; ++i) + { + uint16_t index = myVec.GetFreeSlotIndex(); + indices.push_back(index); + myVec.GetData(index) = startInt; + myVec.GetData(index) = startFloat; + intValues.insert(startInt); + floatValues.insert(startFloat); + startInt += 1; + startFloat += 1.0f; + } + + uint32_t visitCount = 0; + myVec.ForEach([&](int32_t value) -> bool + { + intValues.erase(value); + ++visitCount; + return true; // keep iterating + }); + + // All ints should have been visited and found in the set + EXPECT_EQ(visitCount, Count); + EXPECT_EQ(intValues.size(), 0); + + visitCount = 0; + myVec.ForEach([&](float value) -> bool + { + floatValues.erase(value); + ++visitCount; + return true; // keep iterating + }); + + // All floats should have been visited and found in the set + EXPECT_EQ(visitCount, Count); + EXPECT_EQ(floatValues.size(), 0); + + visitCount = 0; + myVec.ForEach([&]([[maybe_unused]] int32_t value) -> bool + { + ++visitCount; + return false; // stop iterating + }); + + // Since false is immediately returned, only one element should have been visited. + EXPECT_EQ(visitCount, 1); + + } +} diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_tests_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_tests_files.cmake index 1d94a2ae9e..99f3cf8e1a 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_tests_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_tests_files.cmake @@ -11,6 +11,7 @@ set(FILES Tests/CommonTest.cpp Tests/CoreLights/ShadowmapAtlasTest.cpp Tests/IndexedDataVectorTests.cpp + Tests/MultiIndexedDataVectorTests.cpp Tests/IndexableListTests.cpp Tests/SparseVectorTests.cpp Tests/SkinnedMesh/SkinnedMeshDispatchItemTests.cpp