Browse Source

Add first version of sparse array

Panagiotis Christopoulos Charitos 8 years ago
parent
commit
7ef615f7ce

+ 435 - 0
src/anki/util/SparseArray.h

@@ -0,0 +1,435 @@
+// Copyright (C) 2009-2017, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/util/Assert.h>
+#include <anki/util/Array.h>
+#include <anki/util/Allocator.h>
+#include <utility>
+
+namespace anki
+{
+
+/// @addtogroup util_containers
+/// @{
+
+/// A single bucket of SparseArray elements. It's its own class in case we allow multiple buckets in the future.
+/// @internal
+template<typename TNode, PtrSize TBucketSize>
+class SparseArrayBucket
+{
+public:
+	Array<TNode*, TBucketSize> m_elements;
+};
+
+/// SparseArray node.
+/// @internal
+template<typename T>
+class SparseArrayNode
+{
+public:
+	SparseArrayNode* m_saLeft = nullptr;
+	SparseArrayNode* m_saRight = nullptr;
+	SparseArrayNode* m_saParent = nullptr; ///< Used for iterating.
+	PtrSize m_saIdx = 0; ///< The index in the sparse array.
+	T m_saValue;
+
+	template<typename... TArgs>
+	SparseArrayNode(TArgs&&... args)
+		: m_saValue(std::forward<TArgs>(args)...)
+	{
+	}
+
+	T& getSaValue()
+	{
+		return m_saValue;
+	}
+
+	const T& getSaValue() const
+	{
+		return m_saValue;
+	}
+};
+
+/// Sparse array iterator.
+template<typename TNodePointer, typename TValuePointer, typename TValueReference, typename TSparseArrayBasePtr>
+class SparseArrayIterator
+{
+	template<typename, typename, typename, PtrSize, PtrSize>
+	friend class SparseArrayBase;
+
+	template<typename, typename, PtrSize, PtrSize>
+	friend class SparseArray;
+
+public:
+	/// Default constructor.
+	SparseArrayIterator()
+		: m_node(nullptr)
+		, m_array(nullptr)
+	{
+	}
+
+	/// Copy.
+	SparseArrayIterator(const SparseArrayIterator& b)
+		: m_node(b.m_node)
+		, m_array(b.m_array)
+	{
+	}
+
+	/// Allow conversion from iterator to const iterator.
+	template<typename YNodePointer, typename YValuePointer, typename YValueReference, typename YSparseArrayBasePtr>
+	SparseArrayIterator(const SparseArrayIterator<YNodePointer, YValuePointer, YValueReference, YSparseArrayBasePtr>& b)
+		: m_node(b.m_node)
+		, m_array(b.m_array)
+	{
+	}
+
+	SparseArrayIterator(TNodePointer node, TSparseArrayBasePtr arr)
+		: m_node(node)
+		, m_array(arr)
+	{
+		ANKI_ASSERT(arr);
+	}
+
+	TValueReference operator*() const
+	{
+		ANKI_ASSERT(m_node && m_array);
+		return m_node->getSaValue();
+	}
+
+	TValuePointer operator->() const
+	{
+		ANKI_ASSERT(m_node && m_array);
+		return &m_node->getSaValue();
+	}
+
+	SparseArrayIterator& operator++()
+	{
+		ANKI_ASSERT(m_node && m_array);
+		m_node = m_array->getNextNode(m_node);
+		return *this;
+	}
+
+	SparseArrayIterator operator++(int)
+	{
+		ANKI_ASSERT(m_node && m_array);
+		SparseArrayIterator out = *this;
+		++(*this);
+		return out;
+	}
+
+	SparseArrayIterator operator+(U n) const
+	{
+		SparseArrayIterator it = *this;
+		while(n-- != 0)
+		{
+			++it;
+		}
+		return it;
+	}
+
+	SparseArrayIterator& operator+=(U n)
+	{
+		while(n-- != 0)
+		{
+			++(*this);
+		}
+		return *this;
+	}
+
+	Bool operator==(const SparseArrayIterator& b) const
+	{
+		return m_node == b.m_node;
+	}
+
+	Bool operator!=(const SparseArrayIterator& b) const
+	{
+		return !(*this == b);
+	}
+
+	operator Bool() const
+	{
+		return m_node != nullptr;
+	}
+
+private:
+	TNodePointer m_node;
+	TSparseArrayBasePtr m_array;
+};
+
+/// Sparse array base class.
+/// @internal
+template<typename T, typename TNode, typename TIndex = U32, PtrSize TBucketSize = 128, PtrSize TLinearProbingSize = 4>
+class SparseArrayBase
+{
+	template<typename, typename, typename, typename>
+	friend class SparseArrayIterator;
+
+public:
+	// Typedefs
+	using Self = SparseArrayBase<T, TNode, TIndex, TBucketSize, TLinearProbingSize>;
+	using Value = T;
+	using Node = TNode;
+	using Index = TIndex;
+	using Iterator = SparseArrayIterator<TNode*, T*, T&, Self*>;
+	using ConstIterator = SparseArrayIterator<const TNode*, const T*, const T&, const Self*>;
+
+	// Some constants
+	static constexpr PtrSize BUCKET_SIZE = TBucketSize;
+	static constexpr PtrSize LINEAR_PROBING_SIZE = TLinearProbingSize;
+
+	/// Get begin.
+	Iterator getBegin()
+	{
+		return Iterator(m_bucket.m_elements[m_firstElementModIdx], this);
+	}
+
+	/// Get begin.
+	ConstIterator getBegin() const
+	{
+		return ConstIterator(m_bucket.m_elements[m_firstElementModIdx], this);
+	}
+
+	/// Get end.
+	Iterator getEnd()
+	{
+		return Iterator(nullptr, this);
+	}
+
+	/// Get end.
+	ConstIterator getEnd() const
+	{
+		return ConstIterator(nullptr, this);
+	}
+
+	/// Get begin.
+	Iterator begin()
+	{
+		return getBegin();
+	}
+
+	/// Get begin.
+	ConstIterator begin() const
+	{
+		return getBegin();
+	}
+
+	/// Get end.
+	Iterator end()
+	{
+		return getEnd();
+	}
+
+	/// Get end.
+	ConstIterator end() const
+	{
+		return getEnd();
+	}
+
+	/// Get the number of elements in the array.
+	PtrSize getSize() const
+	{
+		return m_elementCount;
+	}
+
+	/// Return true if it's empty and false otherwise.
+	Bool isEmpty() const
+	{
+		return m_elementCount != 0;
+	}
+
+protected:
+	SparseArrayBucket<Node, BUCKET_SIZE> m_bucket;
+	Index m_firstElementModIdx = ~Index(0); ///< To start iterating without searching. Points to m_bucket.
+	U32 m_elementCount = 0;
+
+	/// Default constructor.
+	SparseArrayBase()
+	{
+		ANKI_ASSERT(isPowerOfTwo(BUCKET_SIZE));
+		zeroMemory(m_bucket);
+	}
+
+	/// Non-copyable.
+	SparseArrayBase(const SparseArrayBase&) = delete;
+
+	/// Move constructor.
+	SparseArrayBase(SparseArrayBase&& b)
+	{
+		*this = std::move(b);
+	}
+
+	/// Destroy.
+	~SparseArrayBase()
+	{
+#if ANKI_EXTRA_CHECKS
+		zeroMemory(m_bucket);
+#endif
+	}
+
+	/// Non-copyable.
+	SparseArrayBase& operator=(const SparseArrayBase&) = delete;
+
+	/// Move operator.
+	SparseArrayBase& operator=(SparseArrayBase&& b)
+	{
+		if(b.m_elementCount)
+		{
+			memcpy(&m_bucket[0], &b.m_bucket[0], sizeof(m_bucket));
+			m_elementCount = b.m_elementCount;
+			zeroMemory(b.m_bucket);
+			b.m_elementCount = 0;
+		}
+		else
+		{
+			zeroMemory(m_bucket);
+			m_elementCount = 0;
+		}
+
+		return *this;
+	}
+
+	/// Find a place for a node in the array.
+	Node** findPlace(Index idx);
+
+	/// Remove a node.
+	void remove(Iterator it);
+
+	/// Try get an node.
+	const Node* tryGetNode(Index idx) const;
+
+	/// For iterating.
+	const Node* getNextNode(const Node* const node) const;
+
+	/// For iterating.
+	Node* getNextNode(const Node* node)
+	{
+		const Node* out = getNextNode(node);
+		return const_cast<Node*>(out);
+	}
+
+private:
+	static Index mod(const Index idx)
+	{
+		return idx & (BUCKET_SIZE - 1);
+	}
+
+	static void insertToTree(Node* const root, Node* node);
+
+	/// Remove a node from the tree.
+	/// @return The new root node.
+	static Node* removeFromTree(Node* root, Node* del);
+};
+
+/// Sparse array.
+/// @tparam T The type of the valut it will hold.
+/// @tparam TIndex The type of the index. It should be U32 or U64.
+/// @tparam TBucketeSize The number of the preallocated size.
+/// @tparam TLinearProbingSize The number of positions that will be linearly probed when inserting.
+template<typename T, typename TIndex = U32, PtrSize TBucketSize = 128, PtrSize TLinearProbingSize = 4>
+class SparseArray : public SparseArrayBase<T, SparseArrayNode<T>, TIndex, TBucketSize, TLinearProbingSize>
+{
+public:
+	using Base = SparseArrayBase<T, SparseArrayNode<T>, TIndex, TBucketSize, TLinearProbingSize>;
+	using Node = typename Base::Node;
+	using Value = typename Base::Value;
+	using Index = typename Base::Index;
+	using Iterator = typename Base::Iterator;
+	using ConstIterator = typename Base::ConstIterator;
+
+	using Base::BUCKET_SIZE;
+	using Base::LINEAR_PROBING_SIZE;
+
+	SparseArray()
+	{
+	}
+
+	/// Non-copyable.
+	SparseArray(const SparseArray&) = delete;
+
+	/// Move constructor.
+	SparseArray(SparseArray&& b)
+	{
+		*this = std::move(b);
+	}
+
+	/// Destroy. It will do nothing.
+	~SparseArray()
+	{
+		ANKI_ASSERT(m_elementCount == 0 && "Forgot to call destroy");
+	}
+
+	/// Non-copyable.
+	SparseArray& operator=(const SparseArray&) = delete;
+
+	/// Move operator.
+	SparseArray& operator=(SparseArray&& b)
+	{
+		ANKI_ASSERT(m_elementCount == 0 && "Forgot to destroy");
+		static_cast<Base&>(*this) = std::move(static_cast<Base&>(b));
+		return *this;
+	}
+
+	/// Destroy the array and free its elements.
+	template<typename TAlloc>
+	void destroy(TAlloc& alloc);
+
+	/// Set a value to an index.
+	template<typename TAlloc, typename... TArgs>
+	Iterator setAt(TAlloc& alloc, Index idx, TArgs&&... args)
+	{
+		Node* newNode = alloc.template newInstance<Node>(std::forward<TArgs>(args)...);
+		newNode->m_saIdx = idx;
+		Node** place = Base::findPlace(idx);
+		ANKI_ASSERT(place);
+		if(*place)
+		{
+			alloc.deleteInstance(*place);
+		}
+		else
+		{
+			++Base::m_elementCount;
+		}
+		*place = newNode;
+		return Iterator(newNode, this);
+	}
+
+	/// Get an iterator.
+	Iterator getAt(Index idx)
+	{
+		const Node* node = Base::tryGetNode(idx);
+		return Iterator(const_cast<Node*>(node), this);
+	}
+
+	/// Get an iterator.
+	ConstIterator getAt(Index idx) const
+	{
+		const Node* node = Base::tryGetNode(idx);
+		return ConstIterator(node, this);
+	}
+
+	/// Remove an element.
+	template<typename TAlloc>
+	void erase(TAlloc& alloc, Iterator it)
+	{
+		Base::remove(it);
+		alloc.deleteInstance(it.m_node);
+		ANKI_ASSERT(Base::m_elementCount > 0);
+		--m_elementCount;
+	}
+
+private:
+	using Base::m_elementCount;
+	using Base::m_bucket;
+
+	template<typename TAlloc>
+	void destroyInternal(TAlloc& alloc, Node* const root);
+};
+/// @}
+
+} // end namespace anki
+
+#include <anki/util/SparseArray.inl.h>

+ 415 - 0
src/anki/util/SparseArray.inl.h

@@ -0,0 +1,415 @@
+// 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/SparseArray.h>
+
+namespace anki
+{
+
+template<typename T, typename TNode, typename TIndex, PtrSize TBucketSize, PtrSize TLinearProbingSize>
+TNode** SparseArrayBase<T, TNode, TIndex, TBucketSize, TLinearProbingSize>::findPlace(Index idx)
+{
+	// Update the first node
+	const Index modIdx = mod(idx);
+	if(modIdx < m_firstElementModIdx)
+	{
+		m_firstElementModIdx = modIdx;
+	}
+
+	if(m_bucket.m_elements[modIdx] == nullptr || m_bucket.m_elements[modIdx]->m_saIdx == idx)
+	{
+		// We are lucky, found empty spot or something to replace
+		return &m_bucket.m_elements[modIdx];
+	}
+
+	// Do linear probing
+	Index probingIdx = modIdx + 1;
+	while(probingIdx - modIdx < LINEAR_PROBING_SIZE && probingIdx < BUCKET_SIZE)
+	{
+		if(m_bucket.m_elements[probingIdx] == nullptr || m_bucket.m_elements[probingIdx]->m_saIdx == idx)
+		{
+			return &m_bucket.m_elements[probingIdx];
+		}
+
+		++probingIdx;
+	}
+
+	// Check if we can evict a node. This will happen if that node is from linear probing
+	const Index otherModIdx = mod(m_bucket.m_elements[modIdx]->m_saIdx);
+	if(otherModIdx != modIdx)
+	{
+		ANKI_ASSERT(m_bucket.m_elements[modIdx]->m_saLeft == nullptr
+			&& m_bucket.m_elements[modIdx]->m_saRight == nullptr
+			&& m_bucket.m_elements[modIdx]->m_saParent == nullptr
+			&& "Can't be a tree");
+
+		// Do a hack. Chage the other node's idx
+		Node* const otherNode = m_bucket.m_elements[modIdx];
+		const Index otherNodeIdx = otherNode->m_saIdx;
+		otherNode->m_saIdx = idx;
+
+		// ...and try to find a new place for it. If we don't do the trick above findPlace will return the same place
+		Node** newPlace = findPlace(otherNodeIdx);
+		ANKI_ASSERT(*newPlace != m_bucket.m_elements[modIdx]);
+
+		// ...point the other node to the new place and restore it
+		*newPlace = otherNode;
+		otherNode->m_saIdx = otherNodeIdx;
+
+		// ...now the modIdx place is free for out node
+		m_bucket.m_elements[modIdx] = nullptr;
+		return &m_bucket.m_elements[modIdx];
+	}
+
+	// Last thing we can do, need to append to a tree
+	ANKI_ASSERT(m_bucket.m_elements[modIdx]);
+	Node* it = m_bucket.m_elements[modIdx];
+	Node** pit = &m_bucket.m_elements[modIdx];
+	while(true)
+	{
+		if(idx > it->m_saIdx)
+		{
+			// Go to right
+			Node* right = it->m_saRight;
+			if(right)
+			{
+				it = right;
+				pit = &it->m_saRight;
+			}
+			else
+			{
+				return &it->m_saRight;
+			}
+		}
+		else if(idx < it->m_saIdx)
+		{
+			// Go to left
+			Node* left = it->m_saLeft;
+			if(left)
+			{
+				it = left;
+				pit = &it->m_saLeft;
+			}
+			else
+			{
+				return &it->m_saLeft;
+			}
+		}
+		else
+		{
+			// Equal
+			return pit;
+		}
+	}
+
+	ANKI_ASSERT(!!"Shouldn't reach that");
+	return nullptr;
+}
+
+template<typename T, typename TNode, typename TIndex, PtrSize TBucketSize, PtrSize TLinearProbingSize>
+const TNode* SparseArrayBase<T, TNode, TIndex, TBucketSize, TLinearProbingSize>::tryGetNode(Index idx) const
+{
+	const Index modIdx = mod(idx);
+
+	if(m_bucket.m_elements[modIdx] && mod(m_bucket.m_elements[modIdx]->m_saIdx) == modIdx)
+	{
+		// Walk the tree
+		const Node* it = m_bucket.m_elements[modIdx];
+		do
+		{
+			if(it->m_saIdx == idx)
+			{
+				break;
+			}
+			else if(it->m_saIdx < idx)
+			{
+				it = it->m_saLeft;
+			}
+			else
+			{
+				it = it->m_saRight;
+			}
+		} while(it);
+
+		return it;
+	}
+
+	// Search for linear probing
+	const Index endIdx = min(BUCKET_SIZE, modIdx + LINEAR_PROBING_SIZE);
+	for(Index i = modIdx; i < endIdx; ++i)
+	{
+		const Node* const node = m_bucket.m_elements[i];
+		if(node && node->m_saIdx == idx)
+		{
+			return node;
+		}
+	}
+
+	return nullptr;
+}
+
+template<typename T, typename TNode, typename TIndex, PtrSize TBucketSize, PtrSize TLinearProbingSize>
+void SparseArrayBase<T, TNode, TIndex, TBucketSize, TLinearProbingSize>::insertToTree(Node* const root, Node* node)
+{
+	ANKI_ASSERT(root && node);
+	ANKI_ASSERT(root != node);
+	ANKI_ASSERT(mod(root->m_saIdx) == mod(node->m_saIdx) && "Should belong to the same tree");
+
+	const Index nodeIdx = node->m_saIdx;
+	Node* it = root;
+	Bool done = false;
+	do
+	{
+		const Index idx = it->m_saIdx;
+		if(nodeIdx > idx)
+		{
+			// Go to right
+			Node* const right = it->m_saRight;
+			if(right)
+			{
+				it = right;
+			}
+			else
+			{
+				node->m_saParent = it;
+				it->m_saRight = node;
+				done = true;
+			}
+		}
+		else
+		{
+			// Go to left
+			ANKI_ASSERT(idx != nodeIdx && "Can't do that");
+			Node* const left = it->m_saLeft;
+			if(left)
+			{
+				it = left;
+			}
+			else
+			{
+				node->m_saParent = it;
+				it->m_saLeft = node;
+				done = true;
+			}
+		}
+	} while(!done);
+}
+
+template<typename T, typename TNode, typename TIndex, PtrSize TBucketSize, PtrSize TLinearProbingSize>
+TNode* SparseArrayBase<T, TNode, TIndex, TBucketSize, TLinearProbingSize>::removeFromTree(Node* root, Node* del)
+{
+	ANKI_ASSERT(root && del);
+	Node* const parent = del->m_saParent;
+	Node* const left = del->m_saLeft;
+	Node* const right = del->m_saRight;
+
+	if(parent)
+	{
+		// If it has a parent then remove the connection to the parent and insert left and right like regular nodes
+
+		if(parent->m_saLeft == del)
+		{
+			parent->m_saLeft = nullptr;
+		}
+		else
+		{
+			ANKI_ASSERT(parent->m_saRight == del);
+			parent->m_saRight = nullptr;
+		}
+
+		if(left)
+		{
+			ANKI_ASSERT(left->m_saParent == del);
+			left->m_saParent = nullptr;
+			insertToTree(root, left);
+		}
+
+		if(right)
+		{
+			ANKI_ASSERT(right->m_saParent == del);
+			right->m_saParent = nullptr;
+			insertToTree(root, right);
+		}
+	}
+	else
+	{
+		// It's the root node. Make arbitrarily the left root and add the right
+
+		ANKI_ASSERT(del == root && "It must be the root");
+
+		if(left)
+		{
+			left->m_saParent = nullptr;
+			root = left;
+
+			if(right)
+			{
+				right->m_saParent = nullptr;
+				insertToTree(root, right);
+			}
+		}
+		else
+		{
+			if(right)
+			{
+				right->m_saParent = nullptr;
+			}
+
+			root = right;
+		}
+	}
+
+	return root;
+}
+
+template<typename T, typename TNode, typename TIndex, PtrSize TBucketSize, PtrSize TLinearProbingSize>
+void SparseArrayBase<T, TNode, TIndex, TBucketSize, TLinearProbingSize>::remove(Iterator it)
+{
+	ANKI_ASSERT(it.m_node && it.m_array);
+	ANKI_ASSERT(it.m_array == this);
+
+	Node* const node = it.m_node;
+	Node* const parent = node->m_saParent;
+	Node* const left = node->m_saLeft;
+	Node* const right = node->m_saRight;
+
+	const Index modIdx = mod(node->m_saIdx);
+
+	// Update the first node
+	ANKI_ASSERT(m_firstElementModIdx >= modIdx);
+	if(ANKI_UNLIKELY(m_firstElementModIdx == modIdx))
+	{
+		for(Index i = 0; i < BUCKET_SIZE; ++i)
+		{
+			if(m_bucket.m_elements[i])
+			{
+				m_firstElementModIdx = modIdx;
+				break;
+			}
+		}
+	}
+
+	if(parent || left || right)
+	{
+		// In a tree, remove
+
+		Node* root = m_bucket.m_elements[modIdx];
+		ANKI_ASSERT(root);
+		root = removeFromTree(root, node);
+
+		m_bucket.m_elements[modIdx] = root;
+	}
+	else
+	{
+		// Not yet a tree, remove it from the bucket
+
+		const Index endModIdx = min(modIdx + LINEAR_PROBING_SIZE, BUCKET_SIZE);
+		Index i;
+		for(i = modIdx; i < endModIdx; ++i)
+		{
+			if(m_bucket.m_elements[i] == node)
+			{
+				m_bucket.m_elements[i] = nullptr;
+				break;
+			}
+		}
+
+		ANKI_ASSERT(i < endModIdx && "Node not found");
+	}
+}
+
+template<typename T, typename TNode, typename TIndex, PtrSize TBucketSize, PtrSize TLinearProbingSize>
+const TNode* SparseArrayBase<T, TNode, TIndex, TBucketSize, TLinearProbingSize>::getNextNode(
+	const Node* const node) const
+{
+	ANKI_ASSERT(node);
+	const Node* out = nullptr;
+
+	if(node->m_saLeft)
+	{
+		out = node->m_saLeft;
+	}
+	else if(node->m_saRight)
+	{
+		out = node->m_saRight;
+	}
+	else if(node->m_saParent)
+	{
+		// Node without children but with a parent, move up the tree
+
+		out = node;
+		const Node* prevNode;
+		do
+		{
+			prevNode = out;
+			out = out->m_saParent;
+
+			if(out->m_saRight && out->m_saRight != prevNode)
+			{
+				out = out->m_saRight;
+				break;
+			}
+		} while(out->m_saParent);
+	}
+	else
+	{
+		// Base of the tree, move to the next bucket element
+
+		const Index nextIdx = node->m_saIdx + 1u;
+		for(Index i = mod(nextIdx); i < BUCKET_SIZE; ++i)
+		{
+			if(m_bucket.m_elements[i])
+			{
+				out = m_bucket.m_elements[i];
+				break;
+			}
+		}
+	}
+
+	return out;
+}
+
+template<typename T, typename TIndex, PtrSize TBucketSize, PtrSize TLinearProbingSize>
+template<typename TAlloc>
+void SparseArray<T, TIndex, TBucketSize, TLinearProbingSize>::destroyInternal(TAlloc& alloc, Node* const node)
+{
+	ANKI_ASSERT(node);
+
+	Node* const left = node->m_saLeft;
+	Node* const right = node->m_saRight;
+
+	alloc.deleteInstance(node);
+
+	if(left)
+	{
+		destroyInternal(alloc, left);
+	}
+
+	if(right)
+	{
+		destroyInternal(alloc, right);
+	}
+}
+
+template<typename T, typename TIndex, PtrSize TBucketSize, PtrSize TLinearProbingSize>
+template<typename TAlloc>
+void SparseArray<T, TIndex, TBucketSize, TLinearProbingSize>::destroy(TAlloc& alloc)
+{
+	for(Index i = 0; i < BUCKET_SIZE; ++i)
+	{
+		// Destroy the tree
+
+		Node* root = m_bucket.m_elements[i];
+		if(root)
+		{
+			destroyInternal(alloc, root);
+			m_bucket.m_elements[i] = nullptr;
+		}
+	}
+
+	m_elementCount = 0;
+}
+
+} // end namespace anki

+ 1 - 0
src/anki/util/StdTypes.h

@@ -59,6 +59,7 @@ const U MIN_U = std::numeric_limits<U>::min();
 using PtrSize = size_t; ///< Like size_t
 const PtrSize MAX_PTR_SIZE = std::numeric_limits<PtrSize>::max();
 const PtrSize MIN_PTR_SIZE = std::numeric_limits<PtrSize>::min();
+static_assert(sizeof(PtrSize) == sizeof(void*), "Wrong size for size_t");
 
 using F32 = float; ///< Floating point 32bit
 const F32 MAX_F32 = std::numeric_limits<F32>::max();

+ 2 - 2
tests/gr/Gr.cpp

@@ -487,7 +487,7 @@ ANKI_TEST(Gr, ViewportAndScissor)
 				   "the area around the quad");
 	ShaderProgramPtr prog = createProgram(VERT_QUAD_STRIP_SRC, FRAG_SRC, *gr);
 
-	srand(time(NULL));
+	srand(time(nullptr));
 	Array<FramebufferPtr, 4> fb;
 	for(FramebufferPtr& f : fb)
 	{
@@ -547,7 +547,7 @@ ANKI_TEST(Gr, ViewportAndScissor)
 
 ANKI_TEST(Gr, ViewportAndScissorOffscreen)
 {
-	srand(time(NULL));
+	srand(time(nullptr));
 	COMMON_BEGIN()
 
 	ANKI_TEST_LOGI("Expect to see a grey quad appearing in the 4 corners. "

+ 1 - 1
tests/util/HashMap.cpp

@@ -111,7 +111,7 @@ ANKI_TEST(Util, HashMap)
 		HashMap<int, int, Hasher, Compare> akMap;
 		std::vector<int> numbers;
 
-		// Inserd random
+		// Insert random
 		for(U i = 0; i < MAX; ++i)
 		{
 			U num;

+ 105 - 0
tests/util/SparseArray.cpp

@@ -0,0 +1,105 @@
+// 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/SparseArray.h>
+
+ANKI_TEST(Util, SparseArray)
+{
+	HeapAllocator<U8> alloc(allocAligned, nullptr);
+
+	// Set same key
+	{
+		SparseArray<PtrSize> arr;
+
+		arr.setAt(alloc, 1000, 123);
+		auto it = arr.setAt(alloc, 1000, 124);
+		ANKI_TEST_EXPECT_EQ(*arr.getAt(1000), 124);
+		arr.erase(alloc, it);
+	}
+
+	// Check destroy
+	{
+		SparseArray<PtrSize> arr;
+
+		arr.setAt(alloc, 10000, 123);
+		arr.setAt(alloc, 20000, 124);
+		arr.setAt(alloc, 30000, 125);
+
+		ANKI_TEST_EXPECT_EQ(*arr.getAt(10000), 123);
+		ANKI_TEST_EXPECT_EQ(*arr.getAt(20000), 124);
+		ANKI_TEST_EXPECT_EQ(*arr.getAt(30000), 125);
+
+		arr.destroy(alloc);
+	}
+
+	// Do complex insertions
+	{
+		SparseArray<PtrSize, U32, 32, 3> arr;
+
+		arr.setAt(alloc, 32, 1);
+		// Linear probing
+		arr.setAt(alloc, 32 * 2, 2);
+		arr.setAt(alloc, 32 * 3, 3);
+		// Append to a tree
+		arr.setAt(alloc, 32 * 4, 4);
+		// Linear probing
+		arr.setAt(alloc, 32 + 1, 5);
+		// Evict node
+		arr.setAt(alloc, 32 * 2 + 1, 5);
+
+		ANKI_TEST_EXPECT_EQ(arr.getSize(), 6);
+
+		arr.destroy(alloc);
+	}
+
+	// Fuzzy test
+	{
+		const U MAX = 1000;
+		SparseArray<int, U32, 32, 3> arr;
+		std::vector<int> numbers;
+
+		srand(time(nullptr));
+
+		// Insert random
+		for(U i = 0; i < MAX; ++i)
+		{
+			U num;
+			while(1)
+			{
+				num = rand();
+				if(std::find(numbers.begin(), numbers.end(), int(num)) == numbers.end())
+				{
+					// Not found
+					ANKI_TEST_EXPECT_EQ(arr.getAt(num), arr.getEnd());
+					arr.setAt(alloc, num, num);
+					numbers.push_back(num);
+					break;
+				}
+				else
+				{
+					// Found
+					ANKI_TEST_EXPECT_NEQ(arr.getAt(num), arr.getEnd());
+				}
+			}
+		}
+
+		ANKI_TEST_EXPECT_EQ(arr.getSize(), MAX);
+
+		// Remove randomly
+		U count = MAX;
+		while(count--)
+		{
+			U idx = rand() % (count + 1);
+			int num = numbers[idx];
+			numbers.erase(numbers.begin() + idx);
+
+			auto it = arr.getAt(idx);
+			ANKI_TEST_EXPECT_NEQ(it, arr.getEnd());
+			ANKI_TEST_EXPECT_EQ(*it, num);
+			arr.erase(alloc, it);
+		}
+	}
+}