手把手教你架构3D引擎高级篇系列五

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jxw167/article/details/82622046

内存管理给读者介绍完了,其实我们只是简单的用了一个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);
		}

总结

代码的编写都是比较枯燥的,其实也是重复的造轮子,引擎之间都是互相可以借鉴的。学习引擎也是枯燥的,但是我们可以通过代码的编写,逐步领会它的运行原理,这些也是很有趣的事情。这些底层的基本算法封装也是锻炼开发者对算法的理解。通过这些数据结构的封装,可以更深入的理解它们的内部结构,为以后代码的优化铺垫好路。

阅读更多

扫码向博主提问

海洋_

博客专家

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • 3D引擎架构
  • 服务器架构
  • GPU渲染
  • 客户端架构
  • 引擎优化
去开通我的Chat快问
换一批

没有更多推荐了,返回首页