Browse Source

DynamicArray: Make it growable

Panagiotis Christopoulos Charitos 8 years ago
parent
commit
c5b13e03fa
4 changed files with 335 additions and 25 deletions
  1. 70 24
      src/anki/util/DynamicArray.h
  2. 123 0
      src/anki/util/DynamicArray.inl.h
  3. 0 1
      tests/framework/Framework.h
  4. 142 0
      tests/util/DynamicArray.cpp

+ 70 - 24
src/anki/util/DynamicArray.h

@@ -151,6 +151,9 @@ public:
 	using Base::m_size;
 	using typename Base::Value;
 
+	static constexpr F32 GROW_SCALE = 2.0f;
+	static constexpr F32 SHRINK_SCALE = 2.0f;
+
 	DynamicArray()
 		: Base(nullptr, 0)
 	{
@@ -168,7 +171,7 @@ public:
 
 	~DynamicArray()
 	{
-		ANKI_ASSERT(m_data == nullptr && m_size == 0 && "Requires manual destruction");
+		ANKI_ASSERT(m_data == nullptr && m_size == 0 && m_capacity == 0 && "Requires manual destruction");
 	}
 
 	/// Move.
@@ -179,54 +182,55 @@ public:
 		b.m_data = nullptr;
 		m_size = b.m_size;
 		b.m_size = 0;
+		m_capacity = b.m_capacity;
+		b.m_capacity = 0;
 		return *this;
 	}
 
 	// Non-copyable
 	DynamicArray& operator=(const DynamicArray& b) = delete;
 
-	/// Create the array.
+	/// Only create the array. Useful if @a T is non-copyable or movable .
 	template<typename TAllocator>
 	void create(TAllocator alloc, PtrSize size)
 	{
-		ANKI_ASSERT(m_data == nullptr && m_size == 0);
-
+		ANKI_ASSERT(m_data == nullptr && m_size == 0 && m_capacity == 0);
 		if(size > 0)
 		{
 			m_data = alloc.template newArray<Value>(size);
 			m_size = size;
+			m_capacity = size;
 		}
 	}
 
-	/// Create the array.
+	/// Only create the array. Useful if @a T is non-copyable or movable .
 	template<typename TAllocator>
 	void create(TAllocator alloc, PtrSize size, const Value& v)
 	{
-		ANKI_ASSERT(m_data == nullptr && m_size == 0);
-
+		ANKI_ASSERT(m_data == nullptr && m_size == 0 && m_capacity == 0);
 		if(size > 0)
 		{
 			m_data = alloc.template newArray<Value>(size, v);
 			m_size = size;
+			m_capacity = size;
 		}
 	}
 
-	/// Grow the array.
+	/// Grow or create the array. @a T needs to be copyable and moveable.
 	template<typename TAllocator>
-	void resize(TAllocator alloc, PtrSize size)
-	{
-		ANKI_ASSERT(size > 0);
-		DynamicArray newArr;
-		newArr.create(alloc, size);
+	void resize(TAllocator alloc, PtrSize size, const Value& v);
 
-		PtrSize minSize = min<PtrSize>(size, m_size);
-		for(U i = 0; i < minSize; i++)
-		{
-			newArr[i] = std::move((*this)[i]);
-		}
+	/// Grow or create the array. @a T needs to be copyable and moveable.
+	template<typename TAllocator>
+	void resize(TAllocator alloc, PtrSize size);
 
-		destroy(alloc);
-		*this = std::move(newArr);
+	/// Push back value.
+	template<typename TAllocator, typename... TArgs>
+	void emplaceBack(TAllocator alloc, TArgs&&... args)
+	{
+		resizeStorage(alloc, m_size + 1);
+		::new(&m_data[m_size]) Value(std::forward<TArgs>(args)...);
+		++m_size;
 	}
 
 	/// Destroy the array.
@@ -236,14 +240,38 @@ public:
 		if(m_data)
 		{
 			ANKI_ASSERT(m_size > 0);
+			ANKI_ASSERT(m_capacity > 0);
 			alloc.deleteArray(m_data, m_size);
 
 			m_data = nullptr;
 			m_size = 0;
+			m_capacity = 0;
 		}
 
-		ANKI_ASSERT(m_data == nullptr && m_size == 0);
+		ANKI_ASSERT(m_data == nullptr && m_size == 0 && m_capacity == 0);
+	}
+
+	/// Validate it. Will only work when assertions are enabled.
+	void validate() const
+	{
+		if(m_data)
+		{
+			ANKI_ASSERT(m_size > 0 && m_capacity > 0);
+			ANKI_ASSERT(m_size <= m_capacity);
+		}
+		else
+		{
+			ANKI_ASSERT(m_size == 0 && m_capacity == 0);
+		}
 	}
+
+protected:
+	PtrSize m_capacity = 0;
+
+private:
+	/// Resizes the storage but doesn't constructs any elements. Only moves them.
+	template<typename TAllocator>
+	void resizeStorage(TAllocator& alloc, PtrSize size);
 };
 
 /// Dynamic array with automatic destruction. It's the same as DynamicArray but it holds the allocator in order to
@@ -255,6 +283,7 @@ public:
 	using Base = DynamicArray<T>;
 	using Base::m_data;
 	using Base::m_size;
+	using Base::m_capacity;
 	using typename Base::Value;
 
 	template<typename TAllocator>
@@ -285,24 +314,39 @@ public:
 		b.m_data = nullptr;
 		m_size = b.m_size;
 		b.m_size = 0;
+		m_capacity = b.m_capacity;
+		b.m_capacity = 0;
 		m_alloc = b.m_alloc;
 		b.m_alloc = {};
 		return *this;
 	}
 
-	/// Create the array.
+	/// @copydoc DynamicArray::create
 	void create(PtrSize size)
 	{
 		Base::create(m_alloc, size);
 	}
 
-	/// Create the array.
+	/// @copydoc DynamicArray::create
 	void create(PtrSize size, const Value& v)
 	{
 		Base::create(m_alloc, size, v);
 	}
 
-	/// Grow the array.
+	/// @copydoc DynamicArray::resize
+	void resize(PtrSize size, const Value& v)
+	{
+		Base::resize(m_alloc, size, v);
+	}
+
+	/// @copydoc DynamicArray::emplaceBack
+	template<typename... TArgs>
+	void emplaceBack(TArgs&&... args)
+	{
+		Base::emplaceBack(m_alloc, std::forward<TArgs>(args)...);
+	}
+
+	/// @copydoc DynamicArray::resize
 	void resize(PtrSize size)
 	{
 		Base::resize(m_alloc, size);
@@ -381,3 +425,5 @@ public:
 /// @}
 
 } // end namespace anki
+
+#include <anki/util/DynamicArray.inl.h>

+ 123 - 0
src/anki/util/DynamicArray.inl.h

@@ -0,0 +1,123 @@
+// Copyright (C) 2009-2017, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/util/DynamicArray.h>
+
+namespace anki
+{
+
+template<typename T>
+template<typename TAllocator>
+void DynamicArray<T>::resizeStorage(TAllocator& alloc, PtrSize newSize)
+{
+	if(newSize > m_capacity)
+	{
+		// Need to grow
+
+		m_capacity = (newSize > m_capacity * GROW_SCALE) ? newSize : (m_capacity * GROW_SCALE);
+		Value* newStorage =
+			static_cast<Value*>(alloc.getMemoryPool().allocate(m_capacity * sizeof(Value), alignof(Value)));
+
+		// Move old elements to the new storage
+		if(m_data)
+		{
+			for(PtrSize i = 0; i < m_size; ++i)
+			{
+				::new(&newStorage[i]) Value(std::move(m_data[i]));
+				m_data[i].~Value();
+			}
+
+			alloc.getMemoryPool().free(m_data);
+		}
+
+		m_data = newStorage;
+	}
+	else if(newSize < m_size)
+	{
+		ANKI_ASSERT(m_capacity > 0);
+		ANKI_ASSERT(m_size > 0);
+		ANKI_ASSERT(m_data);
+
+		// Delete remaining stuff
+		for(U i = newSize; i < m_size; ++i)
+		{
+			m_data[i].~Value();
+		}
+
+		m_size = newSize;
+
+		if(newSize < m_capacity / SHRINK_SCALE || newSize == 0)
+		{
+			// Need to shrink
+
+			m_capacity = newSize;
+			if(newSize)
+			{
+				Value* newStorage =
+					static_cast<Value*>(alloc.getMemoryPool().allocate(m_capacity * sizeof(Value), alignof(Value)));
+
+				for(PtrSize i = 0; i < m_size; ++i)
+				{
+					::new(&newStorage[i]) Value(std::move(m_data[i]));
+					m_data[i].~Value();
+				}
+
+				alloc.getMemoryPool().free(m_data);
+				m_data = newStorage;
+			}
+			else
+			{
+				alloc.getMemoryPool().free(m_data);
+				m_data = nullptr;
+			}
+		}
+	}
+}
+
+template<typename T>
+template<typename TAllocator>
+void DynamicArray<T>::resize(TAllocator alloc, PtrSize newSize, const Value& v)
+{
+	const Bool willGrow = newSize > m_size;
+	resizeStorage(alloc, newSize);
+
+	if(willGrow)
+	{
+		// Fill with new values
+		for(U i = m_size; i < newSize; ++i)
+		{
+			::new(&m_data[i]) Value(v);
+		}
+
+		m_size = newSize;
+	}
+
+	ANKI_ASSERT(m_size <= m_capacity);
+	ANKI_ASSERT(m_size == newSize);
+}
+
+template<typename T>
+template<typename TAllocator>
+void DynamicArray<T>::resize(TAllocator alloc, PtrSize newSize)
+{
+	const Bool willGrow = newSize > m_size;
+	resizeStorage(alloc, newSize);
+
+	if(willGrow)
+	{
+		// Fill with new values
+		for(U i = m_size; i < newSize; ++i)
+		{
+			::new(&m_data[i]) Value();
+		}
+
+		m_size = newSize;
+	}
+
+	ANKI_ASSERT(m_size <= m_capacity);
+	ANKI_ASSERT(m_size == newSize);
+}
+
+} // end namespace anki

+ 0 - 1
tests/framework/Framework.h

@@ -88,7 +88,6 @@ extern void deleteTesterSingleton();
 #define ANKI_TEST(suiteName_, name_)                                                     \
 	using namespace anki;                                                                \
 	void test_##suiteName_##name_(Test&);                                                \
-                                                                                         \
 	struct Foo##suiteName_##name_                                                        \
 	{                                                                                    \
 		Foo##suiteName_##name_()                                                         \

+ 142 - 0
tests/util/DynamicArray.cpp

@@ -0,0 +1,142 @@
+// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <tests/framework/Framework.h>
+#include <anki/util/DynamicArray.h>
+#include <vector>
+
+namespace anki
+{
+
+static I64 constructor0Count = 0;
+static I64 constructor1Count = 0;
+static I64 constructor2Count = 0;
+static I64 constructor3Count = 0;
+static I64 destructorCount = 0;
+static I64 copyCount = 0;
+static I64 moveCount = 0;
+
+class DynamicArrayFoo
+{
+public:
+	int m_x;
+
+	DynamicArrayFoo()
+		: m_x(0)
+	{
+		++constructor0Count;
+	}
+
+	DynamicArrayFoo(int x)
+		: m_x(x)
+	{
+		++constructor1Count;
+	}
+
+	DynamicArrayFoo(const DynamicArrayFoo& b)
+		: m_x(b.m_x)
+	{
+		++constructor2Count;
+	}
+
+	DynamicArrayFoo(DynamicArrayFoo&& b)
+		: m_x(b.m_x)
+	{
+		b.m_x = 0;
+		++constructor3Count;
+	}
+
+	~DynamicArrayFoo()
+	{
+		++destructorCount;
+	}
+
+	DynamicArrayFoo& operator=(const DynamicArrayFoo& b)
+	{
+		m_x = b.m_x;
+		++copyCount;
+		return *this;
+	}
+
+	DynamicArrayFoo& operator=(DynamicArrayFoo&& b)
+	{
+		m_x = b.m_x;
+		b.m_x = 0;
+		++moveCount;
+		return *this;
+	}
+};
+
+} // end namespace anki
+
+ANKI_TEST(Util, DynamicArray)
+{
+	{
+		HeapAllocator<U8> alloc(allocAligned, nullptr);
+		DynamicArrayAuto<DynamicArrayFoo> arr(alloc);
+
+		arr.resize(0);
+		arr.resize(2, 1);
+		arr.resize(3, 2);
+		arr.resize(4, 3);
+
+		ANKI_TEST_EXPECT_EQ(arr.getSize(), 4);
+		ANKI_TEST_EXPECT_EQ(arr[0].m_x, 1);
+		ANKI_TEST_EXPECT_EQ(arr[1].m_x, 1);
+		ANKI_TEST_EXPECT_EQ(arr[2].m_x, 2);
+		ANKI_TEST_EXPECT_EQ(arr[3].m_x, 3);
+
+		arr.emplaceBack(4);
+		ANKI_TEST_EXPECT_EQ(arr.getSize(), 5);
+		ANKI_TEST_EXPECT_EQ(arr[4].m_x, 4);
+
+		arr.resize(1);
+		arr.resize(0);
+	}
+
+	// Fuzzy
+	{
+		srand(time(nullptr));
+		HeapAllocator<U8> alloc(allocAligned, nullptr);
+		DynamicArrayAuto<DynamicArrayFoo> arr(alloc);
+
+		std::vector<DynamicArrayFoo> vec;
+
+		const U ITERATIONS = 1000000;
+
+		for(U i = 0; i < ITERATIONS; ++i)
+		{
+			const Bool grow = arr.getSize() > 0 && (rand() & 1);
+			PtrSize newSize;
+			U32 value = rand();
+			if(grow)
+			{
+				newSize = vec.size() * randRange(1.0, 4.0);
+			}
+			else
+			{
+				newSize = vec.size() * randRange(0.0, 0.9);
+			}
+
+			vec.resize(newSize, value);
+			arr.resize(newSize, value);
+
+			// Validate
+			ANKI_TEST_EXPECT_EQ(arr.getSize(), vec.size());
+			for(U i = 0; i < arr.getSize(); ++i)
+			{
+				ANKI_TEST_EXPECT_EQ(arr[i].m_x, vec[i].m_x);
+			}
+
+			arr.validate();
+		}
+
+		arr = DynamicArrayAuto<DynamicArrayFoo>(alloc);
+		vec = std::vector<DynamicArrayFoo>();
+		ANKI_TEST_EXPECT_GT(destructorCount, 0);
+		ANKI_TEST_EXPECT_EQ(
+			constructor0Count + constructor1Count + constructor2Count + constructor3Count, destructorCount);
+	}
+}