260 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			260 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #pragma once
 | |
| 
 | |
| #include <ecs/sparse_set.hpp>
 | |
| #include <memory/scope.hpp>
 | |
| 
 | |
| namespace lt::ecs {
 | |
| 
 | |
| using EntityId = uint32_t;
 | |
| 
 | |
| constexpr auto null_entity = std::numeric_limits<EntityId>::max();
 | |
| 
 | |
| /** A registry of components, the heart of an ECS architecture.
 | |
|  *
 | |
|  * @todo(Light): Implement grouping
 | |
|  * @todo(Light): Implement identifier recycling
 | |
|  * @todo(Light): Optimize views/each
 | |
|  * @todo(Light): Support >2 component views
 | |
|  * @todo(Light): Handle more edge cases or specify the undefined behaviors
 | |
|  *
 | |
|  * @ref https://skypjack.github.io/
 | |
|  * @ref https://github.com/alecthomas/entityx
 | |
|  * @ref https://github.com/skypjack/entt
 | |
|  * @ref https://github.com/SanderMertens/flecs
 | |
|  */
 | |
| class Registry
 | |
| {
 | |
| public:
 | |
| 	using UnderlyingSparseSet_T = TypeErasedSparseSet<EntityId>;
 | |
| 
 | |
| 	using Callback_T = std::function<void(Registry &, EntityId)>;
 | |
| 
 | |
| 	template<typename Component_T>
 | |
| 	void connect_on_construct(Callback_T callback)
 | |
| 	{
 | |
| 		m_on_construct_hooks[get_type_id<Component_T>()] = callback;
 | |
| 	}
 | |
| 
 | |
| 	template<typename Component_T>
 | |
| 	void connect_on_destruct(Callback_T callback)
 | |
| 	{
 | |
| 		m_on_destruct_hooks[get_type_id<Component_T>()] = callback;
 | |
| 	}
 | |
| 
 | |
| 	template<typename Component_T>
 | |
| 	void disconnect_on_construct()
 | |
| 	{
 | |
| 		m_on_construct_hooks.erase(get_type_id<Component_T>());
 | |
| 	}
 | |
| 
 | |
| 	template<typename Component_T>
 | |
| 	void disconnect_on_destruct()
 | |
| 	{
 | |
| 		m_on_destruct_hooks.erase(get_type_id<Component_T>());
 | |
| 	}
 | |
| 
 | |
| 	auto create_entity() -> EntityId
 | |
| 	{
 | |
| 		++m_entity_count;
 | |
| 		return m_current++;
 | |
| 	}
 | |
| 
 | |
| 	void destroy_entity(EntityId entity)
 | |
| 	{
 | |
| 		for (const auto &[key, set] : m_sparsed_sets)
 | |
| 		{
 | |
| 			set->remove(entity);
 | |
| 		}
 | |
| 
 | |
| 		--m_entity_count;
 | |
| 	}
 | |
| 
 | |
| 	template<typename Component_T>
 | |
| 	auto get(EntityId entity) const -> const Component_T &
 | |
| 	{
 | |
| 		auto &derived_set = get_derived_set<Component_T>();
 | |
| 		return derived_set.at(entity).second;
 | |
| 	}
 | |
| 
 | |
| 	template<typename Component_T>
 | |
| 	auto get(EntityId entity) -> Component_T &
 | |
| 	{
 | |
| 		auto &derived_set = get_derived_set<Component_T>();
 | |
| 		return derived_set.at(entity).second;
 | |
| 	}
 | |
| 
 | |
| 	template<typename Component_T>
 | |
| 	auto add(EntityId entity, Component_T component) -> Component_T &
 | |
| 	{
 | |
| 		auto &derived_set = get_derived_set<Component_T>();
 | |
| 		auto &added_component = derived_set.insert(entity, std::move(component)).second;
 | |
| 
 | |
| 		if (m_on_construct_hooks.contains(get_type_id<Component_T>()))
 | |
| 		{
 | |
| 			m_on_construct_hooks[get_type_id<Component_T>()](*this, entity);
 | |
| 		}
 | |
| 
 | |
| 		return added_component;
 | |
| 	};
 | |
| 
 | |
| 	template<typename Component_T>
 | |
| 	void remove(EntityId entity)
 | |
| 	{
 | |
| 		if (m_on_destruct_hooks.contains(get_type_id<Component_T>()))
 | |
| 		{
 | |
| 			m_on_destruct_hooks[get_type_id<Component_T>()](*this, entity);
 | |
| 		}
 | |
| 
 | |
| 		auto &derived_set = get_derived_set<Component_T>();
 | |
| 		derived_set.remove(entity);
 | |
| 	}
 | |
| 
 | |
| 	template<typename Component_T>
 | |
| 	auto view() -> SparseSet<Component_T, EntityId> &
 | |
| 	{
 | |
| 		return get_derived_set<Component_T>();
 | |
| 	};
 | |
| 
 | |
| 	template<typename ComponentA_T, typename ComponentB_T>
 | |
| 	    requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
 | |
| 	auto view() -> std::vector<std::tuple<EntityId, ComponentA_T &, ComponentB_T &>>
 | |
| 	{
 | |
| 		auto &set_a = get_derived_set<ComponentA_T>();
 | |
| 		auto &set_b = get_derived_set<ComponentB_T>();
 | |
| 		auto view = std::vector<std::tuple<EntityId, ComponentA_T &, ComponentB_T &>> {};
 | |
| 
 | |
| 		/* iterate over the "smaller" component-set, and check if its entities have the other
 | |
| 		 * component */
 | |
| 		if (set_a.get_size() > set_b.get_size())
 | |
| 		{
 | |
| 			for (auto &[entity, component_b] : set_b)
 | |
| 			{
 | |
| 				if (set_a.contains(entity))
 | |
| 				{
 | |
| 					view.emplace_back(std::tie(entity, set_a.at(entity).second, component_b));
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			for (auto &[entity, component_a] : set_a)
 | |
| 			{
 | |
| 				if (set_b.contains(entity))
 | |
| 				{
 | |
| 					view.emplace_back(std::tie(entity, component_a, set_b.at(entity).second));
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return view;
 | |
| 	};
 | |
| 
 | |
| 	template<typename Component_T>
 | |
| 	void each(std::function<void(EntityId, Component_T &)> functor)
 | |
| 	{
 | |
| 		for (auto &[entity, component] : get_derived_set<Component_T>())
 | |
| 		{
 | |
| 			functor(entity, component);
 | |
| 		}
 | |
| 	};
 | |
| 
 | |
| 	template<typename ComponentA_T, typename ComponentB_T>
 | |
| 	    requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
 | |
| 	void each(std::function<void(EntityId, ComponentA_T &, ComponentB_T &)> functor)
 | |
| 	{
 | |
| 		auto &set_a = get_derived_set<ComponentA_T>();
 | |
| 		auto &set_b = get_derived_set<ComponentB_T>();
 | |
| 
 | |
| 		/* iterate over the "smaller" component-set, and check if its entities have the other
 | |
| 		 * component */
 | |
| 		if (set_a.get_size() > set_b.get_size())
 | |
| 		{
 | |
| 			for (auto &[entity, component_b] : set_b)
 | |
| 			{
 | |
| 				if (set_a.contains(entity))
 | |
| 				{
 | |
| 					functor(entity, set_a.at(entity).second, component_b);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		for (auto &[entity, component_a] : set_a)
 | |
| 		{
 | |
| 			if (set_b.contains(entity))
 | |
| 			{
 | |
| 				functor(entity, component_a, set_b.at(entity).second);
 | |
| 			}
 | |
| 		}
 | |
| 	};
 | |
| 
 | |
| 	[[nodiscard]] auto get_entity_count() const -> size_t
 | |
| 	{
 | |
| 		return static_cast<size_t>(m_entity_count);
 | |
| 	}
 | |
| 
 | |
| private:
 | |
| 	using TypeId = size_t;
 | |
| 
 | |
| 	static consteval auto hash_cstr(const char *str) -> TypeId
 | |
| 	{
 | |
| 		constexpr auto fnv_offset_basis = size_t { 14695981039346656037ull };
 | |
| 		constexpr auto fnv_prime = size_t { 1099511628211ull };
 | |
| 
 | |
| 		auto hash = fnv_offset_basis;
 | |
| 
 | |
| 		for (const auto &ch : std::string_view { str })
 | |
| 		{
 | |
| 			hash *= fnv_prime;
 | |
| 			hash ^= static_cast<uint8_t>(ch);
 | |
| 		}
 | |
| 
 | |
| 		return hash;
 | |
| 	}
 | |
| 
 | |
| 	template<typename T>
 | |
| 	static consteval auto get_type_id() -> TypeId
 | |
| 	{
 | |
| #if defined _MSC_VER
 | |
| 	#define GENERATOR_PRETTY_FUNCTION __FUNCSIG__
 | |
| #elif defined __clang__ || (defined __GNUC__)
 | |
| 	#define GENERATOR_PRETTY_FUNCTION __PRETTY_FUNCTION__
 | |
| #else
 | |
| 	#error "Compiler not supported"
 | |
| #endif
 | |
| 		constexpr auto value = hash_cstr(GENERATOR_PRETTY_FUNCTION);
 | |
| 
 | |
| #undef GENERATOR_PRETTY_FUNCTION
 | |
| 
 | |
| 		return value;
 | |
| 	}
 | |
| 
 | |
| 	template<typename T>
 | |
| 	auto get_derived_set() -> SparseSet<T, EntityId> &
 | |
| 	{
 | |
| 		constexpr auto type_id = get_type_id<T>();
 | |
| 		if (!m_sparsed_sets.contains(type_id))
 | |
| 		{
 | |
| 			m_sparsed_sets[type_id] = memory::create_scope<SparseSet<T, EntityId>>();
 | |
| 		}
 | |
| 
 | |
| 		auto *base_set = m_sparsed_sets[type_id].get();
 | |
| 		auto *derived_set = dynamic_cast<SparseSet<T, EntityId> *>(base_set);
 | |
| 		ensure(derived_set, "Failed to downcast to derived set");
 | |
| 
 | |
| 		return *derived_set;
 | |
| 	}
 | |
| 
 | |
| 	EntityId m_current;
 | |
| 
 | |
| 	TypeId m_entity_count;
 | |
| 
 | |
| 	std::flat_map<TypeId, memory::Scope<UnderlyingSparseSet_T>> m_sparsed_sets;
 | |
| 
 | |
| 	std::flat_map<TypeId, Callback_T> m_on_construct_hooks;
 | |
| 
 | |
| 	std::flat_map<TypeId, Callback_T> m_on_destruct_hooks;
 | |
| };
 | |
| 
 | |
| } // namespace lt::ecs
 |