内存管理给读者介绍完了,其实我们只是简单的用了一个HashTable哈希表对资源做了一个统一管理,哈希表有自己的封装,因为我们是自己写引擎,在此我们自己实现了一遍,自己封装的优点是便于控制,缺点是要优化好效率问题。我们的Hash表采用了迭代器,由于该类代码量比较大,在此只把关键的几个函数显示一下,其他内容读者可自行查看。说说它的设计思想,我们实现时,并不是简单的实现一个数据结构,而是将它们进行了模块划分,每个模块都有自己的任务,目的是便于后期的扩展以及维护,同时我们的哈希表是为整个引擎服务的,它不是为某个类型服务的,它是为所有类型服务的。它主要分为以下几部分:
HashNode,HashFunc,HashMap,ConstHashMapIterator
我们的Hash表,是广泛被使用的,它不局限于某个类型,因此我们采用了模板的设计方式:
template
struct HashNode
{
typedef HashNode 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类,参考了https://gist.github.com/badboy/6267743的写法。给读者展示一下代码吧,如下所示:
template<>
struct HashFunc
{
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
{
static u32 get(const char* key)
{
u32 result = 0x55555555;
while (*key)
{
result ^= *key++;
result = ((result << 5) | (result >> 27));
}
return result;
}
};
都是一些模板函数。
HashMap和HashMapIterator是哈希表和哈希迭代器,主要是用于管理我们前面定义的节点的
代码如下所示:
template>
class HashMap
{
public:
typedef T value_type;
typedef K key_type;
typedef Hasher hasher_type;
typedef HashMap my_type;
typedef HashNode node_type;
typedef u32 size_type;
friend class HashMapIterator;
friend class ConstHashMapIterator;
static const size_type s_default_ids_count = 8;
template
class HashMapIterator
{
public:
typedef U key_type;
typedef S value_type;
typedef _Hasher hasher_type;
typedef HashNode node_type;
typedef HashMap hm_type;
typedef HashMapIterator 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()
{
}
关于哈希表的实现就完成了,其实网上也有很多这方面的文章,这里就介绍到这里。下面介绍数据结构的队列封装:
队列是一种常见数据结构,它的特点是先进先出,实现队列的封装,使用的也是模板类,代码功能主要是将队列的主要功能实现出来,代码如下所示:
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];
}
实现了队列是否为空,是否满,加入队列,出队列等等。数据结构另一个常用的是列表
列表可以用数组或者指针表示,分配列表空间,删除列表,插入节点,删除节点等等这是列表的一些常用功能,我们引擎封装的列表主要针对内存分配这块,可以采用Chunk的形式进行内存分配。实现代码如下所示:
explicit FreeList(IAllocator& allocator)
: m_allocator(allocator)
{
m_heap = static_cast(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(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);
}
代码的编写都是比较枯燥的,其实也是重复的造轮子,引擎之间都是互相可以借鉴的。学习引擎也是枯燥的,但是我们可以通过代码的编写,逐步领会它的运行原理,这些也是很有趣的事情。这些底层的基本算法封装也是锻炼开发者对算法的理解。通过这些数据结构的封装,可以更深入的理解它们的内部结构,为以后代码的优化铺垫好路。