173 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			173 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#pragma once
 | 
						|
 | 
						|
namespace lt::ecs {
 | 
						|
 | 
						|
/**
 | 
						|
 *
 | 
						|
 * @ref https://programmingpraxis.com/2012/03/09/sparse-sets/
 | 
						|
 */
 | 
						|
template<typename Identifier_T = uint32_t>
 | 
						|
class TypeErasedSparseSet
 | 
						|
{
 | 
						|
public:
 | 
						|
	TypeErasedSparseSet() = default;
 | 
						|
 | 
						|
	TypeErasedSparseSet(TypeErasedSparseSet &&) = default;
 | 
						|
 | 
						|
	TypeErasedSparseSet(const TypeErasedSparseSet &) = default;
 | 
						|
 | 
						|
	auto operator=(TypeErasedSparseSet &&) -> TypeErasedSparseSet & = default;
 | 
						|
 | 
						|
	auto operator=(const TypeErasedSparseSet &) -> TypeErasedSparseSet & = default;
 | 
						|
 | 
						|
	virtual ~TypeErasedSparseSet() = default;
 | 
						|
 | 
						|
	virtual void remove(Identifier_T identifier) = 0;
 | 
						|
};
 | 
						|
 | 
						|
template<typename Value_T, typename Identifier_T = uint32_t>
 | 
						|
class SparseSet: public TypeErasedSparseSet<Identifier_T>
 | 
						|
{
 | 
						|
public:
 | 
						|
	using Dense_T = std::pair<Identifier_T, Value_T>;
 | 
						|
 | 
						|
	static constexpr auto max_capacity = size_t { 1'000'000 };
 | 
						|
 | 
						|
	static constexpr auto null_identifier = std::numeric_limits<Identifier_T>().max();
 | 
						|
 | 
						|
	explicit SparseSet(size_t initial_capacity = 1)
 | 
						|
	{
 | 
						|
		ensure(
 | 
						|
		    initial_capacity <= max_capacity,
 | 
						|
		    "Failed to create SparseSet: capacity too large ({} > {})",
 | 
						|
		    initial_capacity,
 | 
						|
		    max_capacity
 | 
						|
		);
 | 
						|
 | 
						|
		m_dense.reserve(initial_capacity);
 | 
						|
		m_sparse.resize(initial_capacity, null_identifier);
 | 
						|
	}
 | 
						|
 | 
						|
	auto insert(Identifier_T identifier, Value_T value) -> Dense_T &
 | 
						|
	{
 | 
						|
		if (m_sparse.size() < identifier + 1)
 | 
						|
		{
 | 
						|
			auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2);
 | 
						|
			new_capacity = std::min(new_capacity, max_capacity);
 | 
						|
 | 
						|
			// log_dbg("Increasing sparse vector size:", m_dead_count);
 | 
						|
			// log_dbg("\tdead_count: {}", m_dead_count);
 | 
						|
			// log_dbg("\talive_count: {}", m_alive_count);
 | 
						|
			// log_dbg("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
 | 
						|
 | 
						|
			m_sparse.resize(new_capacity, null_identifier);
 | 
						|
		}
 | 
						|
 | 
						|
		++m_alive_count;
 | 
						|
		m_sparse[identifier] = m_dense.size();
 | 
						|
		return m_dense.emplace_back(identifier, std::move(value));
 | 
						|
	}
 | 
						|
 | 
						|
	/** @warn invalidates begin/end iterators
 | 
						|
	 *
 | 
						|
	 * @todo(Light): make it not invalidate the iterators >:c
 | 
						|
	 */
 | 
						|
	void remove(Identifier_T identifier) override
 | 
						|
	{
 | 
						|
		auto &idx = m_sparse[identifier];
 | 
						|
		auto &[entity, component] = m_dense[idx];
 | 
						|
 | 
						|
		auto &[last_entity, last_component] = m_dense.back();
 | 
						|
		auto &last_idx = m_sparse[last_entity];
 | 
						|
 | 
						|
		// removed entity is in dense's back, just pop and invalidate sparse[identifier]
 | 
						|
		if (entity == last_entity)
 | 
						|
		{
 | 
						|
			idx = null_identifier;
 | 
						|
			m_dense.pop_back();
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			// swap dense's 'back' to 'removed'
 | 
						|
			std::swap(component, last_component);
 | 
						|
			entity = last_entity;
 | 
						|
 | 
						|
			// make sparse point to new idx
 | 
						|
			last_idx = idx;
 | 
						|
 | 
						|
			// pop dense and invalidate sparse[identifier]
 | 
						|
			idx = null_identifier;
 | 
						|
			m_dense.pop_back();
 | 
						|
		}
 | 
						|
 | 
						|
		++m_dead_count;
 | 
						|
		--m_alive_count;
 | 
						|
	}
 | 
						|
 | 
						|
	void clear()
 | 
						|
	{
 | 
						|
		m_dense.clear();
 | 
						|
		m_sparse.clear();
 | 
						|
		m_alive_count = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	[[nodiscard]] auto contains(Identifier_T identifier) const -> bool
 | 
						|
	{
 | 
						|
		return m_sparse.size() > identifier               //
 | 
						|
		       && m_sparse[identifier] != null_identifier //
 | 
						|
		       && m_dense[m_sparse[identifier]].first == identifier;
 | 
						|
	}
 | 
						|
 | 
						|
	auto begin() -> std::vector<Dense_T>::iterator
 | 
						|
	{
 | 
						|
		return m_dense.begin();
 | 
						|
	}
 | 
						|
 | 
						|
	auto end() -> std::vector<Dense_T>::iterator
 | 
						|
	{
 | 
						|
		return m_dense.end();
 | 
						|
	}
 | 
						|
 | 
						|
	[[nodiscard]] auto at(Identifier_T identifier) const -> const Dense_T &
 | 
						|
	{
 | 
						|
		return m_dense.at(m_sparse.at(identifier));
 | 
						|
	}
 | 
						|
 | 
						|
	[[nodiscard]] auto at(Identifier_T identifier) -> Dense_T &
 | 
						|
	{
 | 
						|
		return m_dense.at(m_sparse.at(identifier));
 | 
						|
	}
 | 
						|
 | 
						|
	/** @warn unsafe, for bound-checked access: use `.at` */
 | 
						|
	[[nodiscard]] auto &&operator[](this auto &&self, Identifier_T identifier)
 | 
						|
	{
 | 
						|
		using Self_T = decltype(self);
 | 
						|
		return std::forward<Self_T>(self).m_dense[std::forward<Self_T>(self).m_sparse[identifier]];
 | 
						|
	}
 | 
						|
 | 
						|
	[[nodiscard]] auto get_size() const noexcept -> size_t
 | 
						|
	{
 | 
						|
		return m_alive_count;
 | 
						|
	}
 | 
						|
 | 
						|
	[[nodiscard]] auto get_capacity() const noexcept -> size_t
 | 
						|
	{
 | 
						|
		return m_sparse.capacity();
 | 
						|
	}
 | 
						|
 | 
						|
	[[nodiscard]] auto is_empty() const noexcept -> bool
 | 
						|
	{
 | 
						|
		return m_dense.empty();
 | 
						|
	}
 | 
						|
 | 
						|
private:
 | 
						|
	std::vector<Dense_T> m_dense;
 | 
						|
 | 
						|
	std::vector<Identifier_T> m_sparse;
 | 
						|
 | 
						|
	size_t m_alive_count {};
 | 
						|
 | 
						|
	size_t m_dead_count {};
 | 
						|
};
 | 
						|
 | 
						|
} // namespace lt::ecs
 |