Browse Source

WIP: Use a buffer for quad geometry

Michael Ragazzon 3 years ago
parent
commit
c5e05fe0a4
2 changed files with 779 additions and 5 deletions
  1. 180 5
      Backends/RmlUi_Renderer_GL3.cpp
  2. 599 0
      Backends/flat_handle_map.h

+ 180 - 5
Backends/RmlUi_Renderer_GL3.cpp

@@ -27,6 +27,7 @@
  */
 
 #include "RmlUi_Renderer_GL3.h"
+#include "flat_handle_map.h"
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/FileInterface.h>
 #include <RmlUi/Core/Log.h>
@@ -94,6 +95,10 @@ void main() {
 }
 )";
 
+using Quad = Rml::Array<Rml::Vertex, 4>;
+using QuadHandle = flat_handle_map<Quad>::handle;
+static const QuadHandle InvalidQuadHandle = QuadHandle(-1);
+
 namespace Gfx {
 
 enum class ProgramUniform { Translate, Transform, Tex, Count };
@@ -108,6 +113,7 @@ struct CompiledGeometryData {
 	GLuint vbo;
 	GLuint ibo;
 	GLsizei draw_count;
+	QuadHandle quad_handle;
 };
 
 struct ProgramData {
@@ -312,16 +318,113 @@ static void DestroyShaders(ShadersData& shaders)
 
 } // namespace Gfx
 
+class QuadMap {
+public:
+	bool Initialize()
+	{
+		quads.reserve(reserved_quads);
+
+		const int num_indices = 6;
+		static const int quad_indices[] = {0, 3, 1, 1, 3, 2};
+
+		glGenVertexArrays(1, &vao);
+		glGenBuffers(1, &vbo);
+		glGenBuffers(1, &ibo);
+		glBindVertexArray(vao);
+
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glBufferData(GL_ARRAY_BUFFER, sizeof(Quad) * reserved_quads, nullptr, GL_DYNAMIC_DRAW);
+
+		glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::Position);
+		glVertexAttribPointer((GLuint)Gfx::VertexAttribute::Position, 2, GL_FLOAT, GL_FALSE, sizeof(Rml::Vertex),
+			(const GLvoid*)(offsetof(Rml::Vertex, position)));
+
+		glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::Color0);
+		glVertexAttribPointer((GLuint)Gfx::VertexAttribute::Color0, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Rml::Vertex),
+			(const GLvoid*)(offsetof(Rml::Vertex, colour)));
+
+		glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::TexCoord0);
+		glVertexAttribPointer((GLuint)Gfx::VertexAttribute::TexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(Rml::Vertex),
+			(const GLvoid*)(offsetof(Rml::Vertex, tex_coord)));
+
+		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
+		glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * num_indices, (const void*)quad_indices, GL_STATIC_DRAW);
+		glBindVertexArray(0);
+
+		Gfx::CheckGLError("QuadInitialize");
+		return true;
+	}
+
+	void Shutdown()
+	{
+		glDeleteVertexArrays(1, &vao);
+		glDeleteBuffers(1, &vbo);
+		glDeleteBuffers(1, &ibo);
+	}
+
+	QuadHandle Insert(const Quad& quad)
+	{
+		QuadHandle handle = quads.insert(quad);
+
+		if ((int)handle >= reserved_quads)
+		{
+			reserved_quads = reserved_quads * 2;
+			quads.reserve(reserved_quads);
+
+			glBindBuffer(GL_ARRAY_BUFFER, vbo);
+			glBufferData(GL_ARRAY_BUFFER, sizeof(Quad) * reserved_quads, (const void*)quads.data(), GL_DYNAMIC_DRAW);
+		}
+
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glBufferSubData(GL_ARRAY_BUFFER, sizeof(Quad) * GLintptr(handle), sizeof(Quad), (const void*)(quads.data() + int(handle)));
+
+		Gfx::CheckGLError("QuadInsert");
+
+		return handle;
+	}
+
+	void Render(QuadHandle handle) const
+	{
+		const int num_vertices = 4;
+		const int num_indices = 6;
+
+		glBindVertexArray(vao);
+		glDrawElementsBaseVertex(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, (const GLvoid*)0, num_vertices * int(handle));
+
+		Gfx::CheckGLError("QuadRender");
+	}
+
+	void Erase(QuadHandle handle) { quads.erase(handle); }
+
+private:
+	flat_handle_map<Quad> quads;
+	int reserved_quads = 64;
+
+	GLuint vao = 0;
+	GLuint vbo = 0;
+	GLuint ibo = 0;
+
+} quad_map;
+
+size_t num_quads = 0;
+size_t num_non_quads = 0;
+
 RenderInterface_GL3::RenderInterface_GL3()
 {
 	shaders = Rml::MakeUnique<Gfx::ShadersData>();
 
 	if (!Gfx::CreateShaders(*shaders))
 		shaders.reset();
+
+	quad_map.Initialize();
 }
 
 RenderInterface_GL3::~RenderInterface_GL3()
 {
+	Rml::Log::Message(Rml::Log::LT_INFO, "Quads: %zu   Not quads: %zu", num_quads, num_non_quads);
+	
+	quad_map.Shutdown();
+
 	if (shaders)
 		Gfx::DestroyShaders(*shaders);
 }
@@ -376,9 +479,66 @@ void RenderInterface_GL3::RenderGeometry(Rml::Vertex* vertices, int num_vertices
 	}
 }
 
+#if 0
+struct QuadMap {
+	Rml::Vector<Quad> quads;
+	Rml::Vector<int> handle_lookup;
+	bool is_dirty = false;
+
+	QuadHandle Insert()
+	{
+		QuadHandle result = QuadHandle(handle_lookup.size());
+		auto it_handle = std::find(handle_lookup.begin(), handle_lookup.end(), -1);
+		if (it_handle == handle_lookup.end())
+			handle_lookup.
+
+
+		handle_lookup.push_back(int(quads.size()));
+		quads.push_back({});
+		return result;
+	}
+
+	
+	int GetIndex(QuadHandle handle) const
+	{
+		return handle_lookup[handle];
+	}
+
+	void Erase(QuadHandle handle)
+	{
+		const int erase_index = handle_lookup[handle];
+		handle_lookup[handle] = -1;
+
+		quads.erase(quads.begin() + erase_index);
+
+		for (int& index : handle_lookup)
+			if (index >= erase_index)
+				index -= 1;
+	}
+
+} quad_map;
+#endif
+
 Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices,
 	Rml::TextureHandle texture)
 {
+	static const int quad_indices[] = {0, 3, 1, 1, 3, 2};
+	if (num_vertices == 4 && num_indices == 6 && std::equal(std::begin(quad_indices), std::end(quad_indices), indices))
+	{
+		Quad quad = {vertices[0], vertices[1], vertices[2], vertices[3]};
+		QuadHandle handle = quad_map.Insert(quad);
+
+		Gfx::CompiledGeometryData* geometry = new Gfx::CompiledGeometryData{};
+		geometry->texture = (GLuint)texture;
+		geometry->quad_handle = handle;
+
+		num_quads += 1;
+
+		return (Rml::CompiledGeometryHandle)geometry;
+	}
+
+	num_non_quads += 1;
+
 	constexpr GLenum draw_usage = GL_STATIC_DRAW;
 
 	GLuint vao = 0;
@@ -412,6 +572,7 @@ Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Vertex* ve
 	Gfx::CheckGLError("CompileGeometry");
 
 	Gfx::CompiledGeometryData* geometry = new Gfx::CompiledGeometryData;
+	geometry->quad_handle = InvalidQuadHandle;
 	geometry->texture = (GLuint)texture;
 	geometry->vao = vao;
 	geometry->vbo = vbo;
@@ -440,8 +601,15 @@ void RenderInterface_GL3::RenderCompiledGeometry(Rml::CompiledGeometryHandle han
 		glUniform2fv(shaders->program_color.uniform_locations[(size_t)Gfx::ProgramUniform::Translate], 1, &translation.x);
 	}
 
-	glBindVertexArray(geometry->vao);
-	glDrawElements(GL_TRIANGLES, geometry->draw_count, GL_UNSIGNED_INT, (const GLvoid*)0);
+	if (geometry->quad_handle == InvalidQuadHandle)
+	{
+		glBindVertexArray(geometry->vao);
+		glDrawElements(GL_TRIANGLES, geometry->draw_count, GL_UNSIGNED_INT, (const GLvoid*)0);
+	}
+	else
+	{
+		quad_map.Render(geometry->quad_handle);
+	}
 
 	Gfx::CheckGLError("RenderCompiledGeometry");
 }
@@ -450,9 +618,16 @@ void RenderInterface_GL3::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle ha
 {
 	Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle;
 
-	glDeleteVertexArrays(1, &geometry->vao);
-	glDeleteBuffers(1, &geometry->vbo);
-	glDeleteBuffers(1, &geometry->ibo);
+	if (geometry->quad_handle == InvalidQuadHandle)
+	{
+		glDeleteVertexArrays(1, &geometry->vao);
+		glDeleteBuffers(1, &geometry->vbo);
+		glDeleteBuffers(1, &geometry->ibo);
+	}
+	else
+	{
+		quad_map.Erase(geometry->quad_handle);
+	}
 
 	delete geometry;
 }

+ 599 - 0
Backends/flat_handle_map.h

@@ -0,0 +1,599 @@
+/*
+ * flat_handle_map
+ *
+ * A container which provides stable handles to inserted elements. Elements are accessed and erased
+ * in constant time from handles. Insert and emplace occur in amortized constant time.
+ *
+ * Handle map operates similar to std::map<handle, T>, however, elements are stored in a vector-like
+ * underlying container. Handles and iterators remain valid even when other elements are added or
+ * erased, but valid elements are not necessarily stored contiguously. References to elements are
+ * invalidated when inserting into the container, but not when erasing.
+ *
+ * Internally, a list of occupied elements are kept. When an element is erased, it is destroyed and
+ * marked as available. During insertion, the first available element is re-used. If they are all
+ * taken, the underlying container size is increased.
+ *
+ * MIT License
+ *
+ * Copyright (c) 2022 Michael R. P. Ragazzon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstddef>
+#include <memory>
+#include <type_traits>
+#include <vector>
+
+template <typename T, class Alloc = std::allocator<T>>
+struct flat_handle_map {
+public:
+	enum class handle : size_t {};
+
+	class const_iterator {
+	private:
+		const flat_handle_map& container;
+		size_t i;
+
+	public:
+		using iterator_category = std::forward_iterator_tag;
+		using value_type = T;
+		using difference_type = std::ptrdiff_t;
+		using pointer = const T*;
+		using reference = const T&;
+
+		explicit const_iterator(const flat_handle_map& container, size_t i) : container(container), i(i) {}
+		reference operator*() const { return *(container.data() + i); }
+		bool operator==(const const_iterator& other) const { return i == other.i; }
+		bool operator!=(const const_iterator& other) const { return !(*this == other); }
+		const_iterator operator++(int)
+		{
+			const_iterator retval = *this;
+			++(*this);
+			return retval;
+		}
+		const_iterator& operator++()
+		{
+			++i;
+			i = container.get_next_occupied(i);
+			return *this;
+		}
+		handle get_handle() const { return static_cast<handle>(i); }
+	};
+
+	class iterator {
+	private:
+		flat_handle_map& container;
+		size_t i;
+
+	public:
+		using iterator_category = std::forward_iterator_tag;
+		using value_type = T;
+		using difference_type = std::ptrdiff_t;
+		using pointer = T*;
+		using reference = T&;
+
+		explicit iterator(flat_handle_map& container, size_t i) : container(container), i(i) {}
+		reference operator*() const { return *(container.data() + i); }
+		bool operator==(const iterator& other) const { return i == other.i; }
+		bool operator!=(const iterator& other) const { return !(*this == other); }
+		iterator operator++(int)
+		{
+			iterator retval = *this;
+			++(*this);
+			return retval;
+		}
+		iterator& operator++()
+		{
+			++i;
+			i = container.get_next_occupied(i);
+			return *this;
+		}
+		operator const_iterator() const { return const_iterator{container, i}; }
+		handle get_handle() const { return static_cast<handle>(i); }
+	};
+
+	using AllocatorTraits = std::allocator_traits<Alloc>;
+	using allocator_type = Alloc;
+	using value_type = typename AllocatorTraits::value_type;
+	using size_type = typename AllocatorTraits::size_type;
+	using difference_type = typename AllocatorTraits::difference_type;
+	using reference = typename value_type&;
+	using const_reference = typename const value_type&;
+	using pointer = typename AllocatorTraits::pointer;
+	using const_pointer = typename AllocatorTraits::const_pointer;
+
+	flat_handle_map() : flat_handle_map(Alloc()) {}
+
+	flat_handle_map(const Alloc& alloc) : m_capacity(0), m_alloc(alloc) { m_begin = m_end = nullptr; }
+
+	explicit flat_handle_map(size_t count, const T& value, const Alloc& alloc = Alloc()) : flat_handle_map(alloc) { assign_impl(count, value); }
+
+	template <class InputIterator, typename = decltype(*std::declval<InputIterator>())>
+	flat_handle_map(InputIterator first, InputIterator last, const Alloc& alloc = Alloc()) : flat_handle_map(alloc)
+	{
+		assign_impl(first, last);
+	}
+
+	flat_handle_map(std::initializer_list<T> l, const Alloc& alloc = Alloc()) : flat_handle_map(alloc) { assign_impl(l); }
+
+	flat_handle_map(const flat_handle_map& v) :
+		flat_handle_map(v, std::allocator_traits<Alloc>::select_on_container_copy_construction(v.get_allocator()))
+	{}
+
+	flat_handle_map(const flat_handle_map& v, const Alloc& alloc) : flat_handle_map(alloc) { assign_impl(v); }
+
+	flat_handle_map(flat_handle_map&& v) :
+		m_capacity(v.m_capacity), m_alloc(std::move(v.m_alloc)), m_begin(v.m_begin), m_end(v.m_end), m_occupied(std::move(v.m_occupied)),
+		m_first_available(v.m_first_available)
+	{
+		v.m_begin = v.m_end = nullptr;
+		v.m_capacity = 0;
+		v.m_occupied.clear();
+		v.m_first_available = invalid_index;
+	}
+
+	~flat_handle_map()
+	{
+		clear();
+
+		if (m_begin)
+		{
+			AllocatorTraits::deallocate(m_alloc, m_begin, m_capacity);
+		}
+	}
+
+	flat_handle_map& operator=(const flat_handle_map& v)
+	{
+		if (this == &v)
+		{
+			// prevent self usurp
+			return *this;
+		}
+
+		clear();
+
+		assign_impl(v);
+
+		return *this;
+	}
+
+	flat_handle_map& operator=(flat_handle_map&& v)
+	{
+		clear();
+
+		m_occupied = std::move(v.m_occupied);
+		m_first_available = v.m_first_available;
+
+		m_alloc = std::move(v.m_alloc);
+		m_capacity = v.m_capacity;
+		m_begin = v.m_begin;
+		m_end = v.m_end;
+
+		v.m_begin = v.m_end = nullptr;
+		v.m_capacity = 0;
+		v.m_occupied.clear();
+		v.m_first_available = invalid_index;
+
+		return *this;
+	}
+
+	allocator_type get_allocator() const noexcept { return m_alloc; }
+
+	const_reference at(handle h) const noexcept { return *(m_begin + static_cast<size_t>(h)); }
+	reference at(handle h) noexcept { return *(m_begin + static_cast<size_t>(h)); }
+	const_reference operator[](handle h) const noexcept { return at(h); }
+	reference operator[](handle h) noexcept { return at(h); }
+
+	const_reference front() const noexcept { return at(handle{get_next_occupied(0)}); }
+	reference front() noexcept { return at(handle{get_next_occupied(0)}); }
+	const_reference back() const noexcept { return at(handle{get_previous_occupied(m_occupied.size() - 1)}); }
+	reference back() noexcept { return at(handle{get_previous_occupied(m_occupied.size() - 1)}); }
+
+	const_pointer data() const noexcept { return m_begin; }
+	pointer data() noexcept { return m_begin; }
+
+	// iterators
+	iterator begin() noexcept { return iterator(*this, get_next_occupied(0)); }
+	const_iterator begin() const noexcept { return const_iterator(*this, get_next_occupied(0)); }
+	const_iterator cbegin() const noexcept { return const_iterator(*this, get_next_occupied(0)); }
+
+	iterator end() noexcept { return iterator(*this, invalid_index); }
+	const_iterator end() const noexcept { return const_iterator(*this, invalid_index); }
+	const_iterator cend() const noexcept { return const_iterator(*this, invalid_index); }
+
+	iterator iterate(handle h) noexcept { return iterator(*this, static_cast<size_t>(h)); }
+	const_iterator iterate(handle h) const noexcept { return const_iterator(*this, static_cast<size_t>(h)); }
+
+	// capacity
+	bool empty() const noexcept { return m_begin == m_end || size() == 0; }
+	size_t size() const noexcept { return std::count(m_occupied.begin(), m_occupied.end(), true); }
+	size_t container_size() const noexcept { return m_end - m_begin; }
+
+	void reserve(size_t desired_capacity)
+	{
+		if (desired_capacity <= m_capacity)
+		{
+			return;
+		}
+
+		m_occupied.reserve(desired_capacity);
+
+		size_type new_cap = find_capacity(desired_capacity);
+
+		auto new_buf = AllocatorTraits::allocate(m_alloc, new_cap);
+		const auto s = container_size();
+
+		// now we need to transfer the existing elements into the new buffer
+		for (size_type i = 0; i < s; ++i)
+		{
+			if (m_occupied[i])
+				AllocatorTraits::construct(m_alloc, new_buf + i, std::move(*(m_begin + i)));
+		}
+
+		if (m_begin)
+		{
+			// free old elements
+			for (size_type i = 0; i < s; ++i)
+			{
+				if (m_occupied[i])
+					AllocatorTraits::destroy(m_alloc, m_begin + i);
+			}
+
+			AllocatorTraits::deallocate(m_alloc, m_begin, m_capacity);
+		}
+
+		m_begin = new_buf;
+		m_end = new_buf + s;
+		m_capacity = new_cap;
+	}
+
+	size_t capacity() const noexcept { return m_capacity; }
+
+	// modifiers
+	void clear() noexcept
+	{
+		for (size_t i = 0; i < m_occupied.size(); i++)
+		{
+			if (m_occupied[i])
+				AllocatorTraits::destroy(m_alloc, m_begin + i);
+		}
+		m_occupied.clear();
+		m_first_available = invalid_index;
+		m_end = m_begin;
+	}
+
+	handle insert(const value_type& val)
+	{
+		handle result = static_cast<handle>(invalid_index);
+		if (m_first_available >= container_size())
+		{
+			result = static_cast<handle>(container_size());
+			auto pos = grow_end(1);
+			AllocatorTraits::construct(m_alloc, pos, val);
+			m_occupied.push_back(true);
+			m_first_available = invalid_index;
+		}
+		else
+		{
+			result = static_cast<handle>(m_first_available);
+			AllocatorTraits::construct(m_alloc, m_begin + m_first_available, val);
+			m_occupied[m_first_available] = true;
+			m_first_available = get_next_available(m_first_available + 1);
+		}
+		return result;
+	}
+
+	handle insert(value_type&& val)
+	{
+		handle result{invalid_index};
+		if (m_first_available >= container_size())
+		{
+			result = static_cast<handle>(container_size());
+			auto pos = grow_end(1);
+			AllocatorTraits::construct(m_alloc, pos, std::move(val));
+			m_occupied.push_back(true);
+			m_first_available = invalid_index;
+		}
+		else
+		{
+			result = static_cast<handle>(m_first_available);
+			AllocatorTraits::construct(m_alloc, m_begin + m_first_available, std::move(val));
+			m_occupied[m_first_available] = true;
+			m_first_available = get_next_available(m_first_available + 1);
+		}
+		return result;
+	}
+
+	void insert(size_type count, const value_type& val)
+	{
+		reserve(size() + count);
+		for (size_type i = 0; i < count; ++i)
+			insert(val);
+	}
+
+	template <typename InputIterator, typename = decltype(*std::declval<InputIterator>())>
+	void insert(InputIterator first, InputIterator last)
+	{
+		reserve(size() + last - first);
+		for (auto p = first; p != last; ++p)
+			insert(*p);
+	}
+
+	void insert(std::initializer_list<T> ilist)
+	{
+		reserve(size() + ilist.size());
+		for (auto& elem : ilist)
+			insert(elem);
+	}
+
+	template <typename... Args>
+	handle emplace(Args&&... args)
+	{
+		handle result{invalid_index};
+		if (m_first_available >= container_size())
+		{
+			result = static_cast<handle>(container_size());
+			auto pos = grow_end(1);
+			AllocatorTraits::construct(m_alloc, pos, std::forward<Args>(args)...);
+			m_occupied.push_back(true);
+			m_first_available = invalid_index;
+		}
+		else
+		{
+			result = static_cast<handle>(m_first_available);
+			AllocatorTraits::construct(m_alloc, m_begin + m_first_available, std::forward<Args>(args)...);
+			m_occupied[m_first_available] = true;
+			m_first_available = get_next_available(m_first_available + 1);
+		}
+		return result;
+	}
+
+	iterator erase(handle h)
+	{
+		auto i = static_cast<size_t>(h);
+		assert(i < m_occupied.size() && m_occupied[i]);
+		AllocatorTraits::destroy(m_alloc, m_begin + i);
+		m_occupied[i] = false;
+		if (i < m_first_available)
+			m_first_available = i;
+		return iterator(*this, get_next_occupied(i + 1));
+	}
+	iterator erase(const_iterator it) { return erase(it.get_handle()); }
+
+private:
+	size_t get_next_available(size_t i) const noexcept
+	{
+		for (size_t j = i; j < m_occupied.size(); j++)
+			if (!m_occupied[j])
+				return j;
+		return invalid_index;
+	}
+	size_t get_next_occupied(size_t i) const noexcept
+	{
+		for (size_t j = i; j < m_occupied.size(); j++)
+			if (m_occupied[j])
+				return j;
+		return invalid_index;
+	}
+	size_t get_previous_occupied(size_t i) const noexcept
+	{
+		static_assert(size_t(0) < size_t(0) - 1, "size_t must underflow when decrementing from zero");
+		for (size_t j = i; j < m_occupied.size(); j--)
+			if (m_occupied[j])
+				return j;
+		return invalid_index;
+	}
+
+	size_type find_capacity(size_type desired_capacity) const
+	{
+		if (m_capacity == 0)
+		{
+			return desired_capacity;
+		}
+		else
+		{
+			auto new_cap = m_capacity;
+
+			while (new_cap < desired_capacity)
+			{
+				// grow by roughly 1.5
+				new_cap *= 3;
+				++new_cap;
+				new_cap /= 2;
+			}
+
+			return new_cap;
+		}
+	}
+
+	T* grow_end(size_t num)
+	{
+		const auto s = container_size();
+		reserve(s + num);
+		m_end = m_begin + s + num;
+		return m_begin + s;
+	}
+
+	// grows buffer only on empty
+	void grow_empty(size_t capacity)
+	{
+		assert(m_begin == m_end);
+
+		if (capacity <= m_capacity)
+			return;
+
+		if (m_begin)
+		{
+			AllocatorTraits::deallocate(m_alloc, m_begin, m_capacity);
+		}
+
+		m_capacity = find_capacity(capacity);
+		m_begin = m_end = AllocatorTraits::allocate(m_alloc, m_capacity);
+	}
+
+	void assign_impl(size_type count, const T& value)
+	{
+		grow_empty(count);
+
+		for (size_type i = 0; i < count; ++i)
+		{
+			insert(value);
+		}
+	}
+
+	template <class InputIterator>
+	void assign_impl(InputIterator first, InputIterator last)
+	{
+		auto isize = last - first;
+		grow_empty(isize);
+
+		for (auto p = first; p != last; ++p)
+		{
+			insert(*p);
+		}
+	}
+
+	void assign_impl(std::initializer_list<T> ilist)
+	{
+		grow_empty(ilist.size());
+
+		for (auto& elem : ilist)
+		{
+			insert(elem);
+		}
+	}
+
+	void assign_impl(const flat_handle_map& v)
+	{
+		grow_empty(v.container_size());
+
+		m_occupied = v.m_occupied;
+		m_first_available = v.m_first_available;
+
+		for (size_t i = 0; i < v.container_size(); i++)
+			if (m_occupied[i])
+				AllocatorTraits::construct(m_alloc, m_begin + i, *(v.m_begin + i));
+
+		m_end = m_begin + v.container_size();
+	}
+
+	template <typename T, class Alloc>
+	friend bool operator==(const flat_handle_map<T, Alloc>& a, const flat_handle_map<T, Alloc>& b);
+
+	pointer m_begin;
+	pointer m_end;
+
+	size_t m_capacity;
+
+	Alloc m_alloc;
+
+	std::vector<bool> m_occupied;
+	size_t m_first_available = invalid_index;
+
+	static constexpr size_t invalid_index = size_t(-1);
+};
+
+template <typename T, class Alloc>
+bool operator==(const flat_handle_map<T, Alloc>& a, const flat_handle_map<T, Alloc>& b)
+{
+	using handle = typename flat_handle_map<T, Alloc>::handle;
+
+	if (a.size() != b.size())
+	{
+		return false;
+	}
+
+	for (size_t i = 0; i < a.m_occupied.size(); ++i)
+	{
+		auto h = static_cast<handle>(i);
+		if (a.m_occupied[i] != b.m_occupied[i])
+			return false;
+		if (a.m_occupied[i] && (a[h] != b[h]))
+			return false;
+	}
+
+	return true;
+}
+
+template <typename T, class Alloc>
+bool operator!=(const flat_handle_map<T, Alloc>& a, const flat_handle_map<T, Alloc>& b)
+{
+	return !(a == b);
+}
+
+#ifdef HANDLEMAP_TEST
+
+void test_stable_vector()
+{
+	flat_handle_map<string> m;
+	flat_handle_map<string> m_copy1 = m;
+	assert(m == m_copy1);
+
+	auto h_hello = m.insert("hello");
+	auto h_carl = m.insert("carl");
+	auto h_wood = m.insert("wood");
+	auto h_space = m.insert(" ");
+	m.insert({"red", "green", "blue", "alpha", "none"});
+	auto h_long = m.insert(
+		"A REAAAALLY long string.........................................................:::::::::::::::::::::::...................................."s);
+	auto h_world = m.insert("world");
+
+	m.erase(h_long);
+	m.insert("short");
+
+	m.insert({"s", "p", "a", "m"});
+	string s = "s";
+	m.insert(s);
+	m.emplace("p");
+	m.insert("a");
+	m.emplace("m");
+
+	for (auto it = m.iterate(h_carl); it != m.end(); ++it)
+	{
+		if (*it == "blue")
+			m.erase(it);
+	}
+
+	m.erase(h_carl);
+	auto h_jane = m.insert("jane");
+	auto h_black = m.insert("black");
+	m.erase(h_wood);
+
+	auto ss = MakeString();
+	for (auto it = m.begin(); it != m.end(); ++it)
+	{
+		ss << *it << ", ";
+	}
+	string str = (string)ss;
+	auto str2 = m[h_hello] + m[h_space] + m[h_world];
+
+	assert(str == "hello, jane,  , red, green, black, alpha, none, short, world, s, p, a, m, s, p, a, m, ");
+	assert(str2 == "hello world");
+
+	flat_handle_map<string> m_copy2{m};
+	assert(m == m_copy2);
+	assert(m_copy1 != m_copy2);
+}
+
+#endif