#pragma once #include <algorithm> #include <cstdint> #include <cstdlib> #include <format> #include <functional> #include <set> #include <stdexcept> #include <unordered_map> #include <vector> namespace zecsy { using entity_id = uint64_t; /* * Using std::set for entities_set to maintain sorted order, which can * improve cache locality during queries and iterations. */ using entities_set = std::set<entity_id>; class component_storage final { public: template<typename T> bool has(entity_id e) const; template<typename First, typename Second, typename... Rest> bool has(entity_id e) const; template<typename T> T& get(entity_id e); template<typename T> void set(entity_id e); template<typename T> void set(entity_id e, const T& comp); template<typename First, typename Second, typename... Rest> void set(entity_id e); template<typename First, typename Second, typename... Rest> void set(entity_id e, const First& comp0, const Second& comp1, const Rest&... rest_comps); template<typename T> void remove(entity_id e); template<typename First, typename Second, typename... Rest> void remove(entity_id e); private: using comp_id = size_t; struct component_pool { std::vector<uint8_t> data; std::vector<size_t> free_list; // Reusable indices std::unordered_map<entity_id, size_t> entity_to_index; std::unordered_map<size_t, entity_id> index_to_entity; }; std::unordered_map<comp_id, component_pool> pools; template<typename T> static comp_id get_component_id() { static comp_id id = next_component_id++; return id; } static comp_id next_component_id; }; inline component_storage::comp_id component_storage::next_component_id = 0; template<typename T> inline bool component_storage::has(entity_id e) const { auto id = get_component_id<T>(); if(pools.contains(id)) { return pools.at(id).entity_to_index.contains(e); } return false; } template<typename T> inline T& component_storage::get(entity_id e) { auto id = get_component_id<T>(); if(!has<T>(e)) { throw std::runtime_error( std::format("Entity #{} doesn't have {}", e, typeid(T).name())); } auto& pool = pools.at(id); auto index = pool.entity_to_index.at(e); return *reinterpret_cast<T*>(&pool.data[index * sizeof(T)]); } template<typename T> inline void component_storage::set(entity_id e, const T& comp) { auto id = get_component_id<T>(); auto& pool = pools[id]; size_t index; if(!pool.free_list.empty()) { index = pool.free_list.back(); pool.free_list.pop_back(); } else { index = pool.data.size() / sizeof(T); pool.data.resize(pool.data.size() + sizeof(T)); } new(&pool.data[index * sizeof(T)]) T(comp); pool.entity_to_index[e] = index; pool.index_to_entity[index] = e; } template<typename T> inline void component_storage::set(entity_id e) { set(e, T{}); } template<typename T> inline void component_storage::remove(entity_id e) { auto id = get_component_id<T>(); if(!has<T>(e)) { return; } auto& pool = pools[id]; auto index = pool.entity_to_index[e]; pool.free_list.push_back(index); pool.entity_to_index.erase(e); pool.index_to_entity.erase(index); } template<typename First, typename Second, typename... Rest> inline bool component_storage::has(entity_id e) const { return has<First>(e) && has<Second>(e) && (has<Rest>(e) && ...); } template<typename First, typename Second, typename... Rest> inline void component_storage::set(entity_id e) { set(e, First{}); set(e, Second{}); (set(e, Rest{}), ...); } template<typename First, typename Second, typename... Rest> inline void component_storage::set(entity_id e, const First& comp0, const Second& comp1, const Rest&... rest_comps) { set(e, comp0); set(e, comp1); (set(e, rest_comps), ...); } template<typename First, typename Second, typename... Rest> inline void component_storage::remove(entity_id e) { remove<First>(e); remove<Second>(e); (remove<Rest>(e), ...); } class system_scheduler final { public: void add_system(float freq, auto&& func); void add_system(int freq, auto&& func); void update(float dt); private: struct system_handler { double interval; double accumulator = 0.0f; std::function<void(float)> callback; }; std::vector<system_handler> systems; }; inline void system_scheduler::add_system(float freq, auto&& func) { systems.emplace_back(1.0f / freq, 0.0f, std::forward<decltype(func)>(func)); } inline void system_scheduler::add_system(int freq, auto&& func) { add_system(float(freq), func); } inline void system_scheduler::update(float dt) { dt = std::max(0.0f, dt); for(auto& s: systems) { s.accumulator += dt; while(s.accumulator >= s.interval) { s.callback(dt); s.accumulator -= s.interval; } } } class world final { public: entity_id make_entity(); void destroy_entity(entity_id e); bool is_alive(entity_id e) const; template<typename... T> bool has(entity_id e) const; template<typename T> T& get(entity_id e); template<typename... T> void set(entity_id e); template<typename... T> void set(entity_id e, const T&... comps); template<typename... T> void remove(entity_id e); template<typename... T> void query(auto&& system); private: entities_set alive_entities; entity_id entity_counter = 0; component_storage storage; }; inline entity_id world::make_entity() { auto id = ++entity_counter; alive_entities.emplace(id); return id; } inline void world::destroy_entity(entity_id e) { alive_entities.erase(e); } inline bool world::is_alive(entity_id e) const { return alive_entities.contains(e); } template<typename... T> inline bool world::has(entity_id e) const { return storage.has<T...>(e); } template<typename T> inline T& world::get(entity_id e) { return storage.get<T>(e); } template<typename... T> inline void world::set(entity_id e) { storage.set<T...>(e); } template<typename... T> inline void world::set(entity_id e, const T&... comps) { storage.set(e, comps...); } template<typename... T> inline void world::remove(entity_id e) { storage.remove<T...>(e); } template<typename... T> inline void world::query(auto&& system) { for(auto e: alive_entities) { if((has<T...>(e))) { system(get<T>(e)...); } } } }; // namespace zecsy