1. 程式人生 > >手把手教你架構3D引擎高階篇系列五

手把手教你架構3D引擎高階篇系列五

記憶體管理給讀者介紹完了,其實我們只是簡單的用了一個HashTable雜湊表對資源做了一個統一管理,雜湊表有自己的封裝,因為我們是自己寫引擎,在此我們自己實現了一遍,自己封裝的優點是便於控制,缺點是要優化好效率問題。我們的Hash表採用了迭代器,由於該類程式碼量比較大,在此只把關鍵的幾個函式顯示一下,其他內容讀者可自行檢視。說說它的設計思想,我們實現時,並不是簡單的實現一個數據結構,而是將它們進行了模組劃分,每個模組都有自己的任務,目的是便於後期的擴充套件以及維護,同時我們的雜湊表是為整個引擎服務的,它不是為某個型別服務的,它是為所有型別服務的。它主要分為以下幾部分:
HashNode,HashFunc,HashMap,ConstHashMapIterator

HashNode

我們的Hash表,是廣泛被使用的,它不侷限於某個型別,因此我們採用了模板的設計方式:

	template <class K, class V>
	struct HashNode
	{
		typedef HashNode<K, V> my_node;

		HashNode(const K& key, const V& value)
			: m_key(key)
			, m_value(value)
			, m_next(nullptr)
		{}

		explicit HashNode(const my_node& src)
			: m_key(src.m_key)
			, m_value(src.m_value)
			, m_next(src.m_next)
		{}

		K m_key;
		V m_value;
		my_node* m_next;
	};

HashFunc

HashFunc類,參考了https://gist.github.com/badboy/6267743的寫法。給讀者展示一下程式碼吧,如下所示:

template<>
	struct HashFunc<void*>
	{
		static u32 get(const void* key)
		{
			#ifdef PLATFORM64
				u64 tmp = (u64)key;
				tmp = (~tmp) + (tmp << 18);
				tmp = tmp ^ (tmp >> 31);
				tmp = tmp * 21;
				tmp = tmp ^ (tmp >> 11);
				tmp = tmp + (tmp << 6);
				tmp = tmp ^ (tmp >> 22);
				return (u32)tmp;
			#else
				size_t x = ((i32(key) >> 16) ^ i32(key)) * 0x45d9f3b;
				x = ((x >> 16) ^ x) * 0x45d9f3b;
				x = ((x >> 16) ^ x);
				return x;
			#endif
		}
	};

	template<>
	struct HashFunc<char*>
	{
		static u32 get(const char* key)
		{
			u32 result = 0x55555555;

			while (*key) 
			{ 
				result ^= *key++;
				result = ((result << 5) | (result >> 27));
			}

			return result;
		}
	};

都是一些模板函式。

HashMap&HashMapIterator

HashMap和HashMapIterator是雜湊表和雜湊迭代器,主要是用於管理我們前面定義的節點的
程式碼如下所示:

template<class K, class T, class Hasher = HashFunc<K>>
	class HashMap
	{
	public:
		typedef T value_type;
		typedef K key_type;
		typedef Hasher hasher_type;
		typedef HashMap<key_type, value_type, hasher_type> my_type;
		typedef HashNode<key_type, value_type> node_type;
		typedef u32 size_type;

		friend class HashMapIterator;
		friend class ConstHashMapIterator;

		static const size_type s_default_ids_count = 8;

		template <class U, class S, class _Hasher>
		class HashMapIterator
		{
		public:
			typedef U key_type;
			typedef S value_type;
			typedef _Hasher hasher_type;
			typedef HashNode<key_type, value_type> node_type;
			typedef HashMap<key_type, value_type, hasher_type> hm_type;
			typedef HashMapIterator<key_type, value_type, hasher_type> my_type;

			friend hm_type;

			HashMapIterator()
				: m_hash_map(nullptr)
				, m_current_node(nullptr)
			{
			}

			HashMapIterator(const my_type& src)
				: m_hash_map(src.m_hash_map)
				, m_current_node(src.m_current_node)
			{
			}

			HashMapIterator(node_type* node, hm_type* hm)
				: m_hash_map(hm)
				, m_current_node(node)
			{
			}

			~HashMapIterator()
			{
			}

關於雜湊表的實現就完成了,其實網上也有很多這方面的文章,這裡就介紹到這裡。下面介紹資料結構的佇列封裝:

queue

佇列是一種常見資料結構,它的特點是先進先出,實現佇列的封裝,使用的也是模板類,程式碼功能主要是將佇列的主要功能實現出來,程式碼如下所示:

		bool full() const { return size() == count; }
		bool empty() const { return m_rd == m_wr; } 
		u32 size() const { return m_wr - m_rd; }
		Iterator begin() { return {this, m_rd}; }
		Iterator end() { return {this, m_wr}; }

		void push(const T& item)
		{
			ASSERT(m_wr - m_rd < count);

			u32 idx = m_wr & (count - 1);
			::new (NewPlaceholder(), &m_buffer[idx]) T(item);
			++m_wr;
		}

		void pop()
		{
			ASSERT(m_wr != m_rd);

			u32 idx = m_rd & (count - 1);
			(&m_buffer[idx])->~T();
			m_rd++;
		}

		T& front()
		{
			u32 idx = m_rd & (count - 1);
			return m_buffer[idx];
		}

		const T& front() const
		{
			u32 idx = m_rd & (count - 1);
			return m_buffer[idx];
		}

		T& back()
		{
			ASSERT(!empty());

			u32 idx = m_wr & (count - 1);
			return m_buffer[idx - 1];
		}

		const T& back() const
		{
			ASSERT(!empty());

			u32 idx = m_wr & (count - 1);
			return m_buffer[idx - 1];
		}

實現了佇列是否為空,是否滿,加入佇列,出佇列等等。資料結構另一個常用的是列表

List

列表可以用陣列或者指標表示,分配列表空間,刪除列表,插入節點,刪除節點等等這是列表的一些常用功能,我們引擎封裝的列表主要針對記憶體分配這塊,可以採用Chunk的形式進行記憶體分配。實現程式碼如下所示:

explicit FreeList(IAllocator& allocator)
			: m_allocator(allocator)
		{
			m_heap = static_cast<T*>(allocator.allocate_aligned(sizeof(T) * chunk_size, ALIGN_OF(T)));
			m_pool_index = chunk_size;

			for (i32 i = 0; i < chunk_size; i++)
			{
				m_pool[i] = &m_heap[i];
			}
		}

		~FreeList()
		{
			m_allocator.deallocate_aligned(m_heap);
		}

		void* allocate(size_t size) override
		{
			ASSERT(size == sizeof(T));
			return m_pool_index > 0 ? m_pool[--m_pool_index] : nullptr;
		}

		void deallocate(void* ptr) override
		{
			ASSERT(((uintptr)ptr >= (uintptr)&m_heap[0]) && ((uintptr)ptr < (uintptr)&m_heap[chunk_size]));
			m_pool[m_pool_index++] = reinterpret_cast<T*>(ptr);
		}

		void* reallocate(void*, size_t) override
		{
			ASSERT(false);
			return nullptr;
		}

		void* allocate_aligned(size_t size, size_t align) override
		{
			void* ptr = allocate(size);
			ASSERT((uintptr)ptr % align == 0);
			return ptr;
		}


		void deallocate_aligned(void* ptr) override
		{
			return deallocate(ptr);
		}


		void* reallocate_aligned(void* ptr, size_t size, size_t align) override
		{
			ASSERT(size <= ALIGN_OF(T));
			return reallocate(ptr, size);
		}

總結

程式碼的編寫都是比較枯燥的,其實也是重複的造輪子,引擎之間都是互相可以借鑑的。學習引擎也是枯燥的,但是我們可以通過程式碼的編寫,逐步領會它的執行原理,這些也是很有趣的事情。這些底層的基本演算法封裝也是鍛鍊開發者對演算法的理解。通過這些資料結構的封裝,可以更深入的理解它們的內部結構,為以後程式碼的優化鋪墊好路。