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