#pragma once #include #include #include #include #include #include #include #include #include #include namespace zecsy { using entity_id = uint64_t; /* * unordered_set is also an option. But probably sorting ids may be * beneficial for queries, because it might increase the chance of reusing * cpu cache (aka "required components were close to each other"). * Definitely should play around with it */ using entities_set = std::set; class component_storage final { public: template bool has(entity_id e) const; template bool has(entity_id e) const; template T& get(entity_id e); template void set(entity_id e); template void set(entity_id e, const T& comp); template void set(entity_id e); template void set(entity_id e, const First& comp0, const Second& comp1, const Rest&... rest_comps); template void remove(entity_id e); template void remove(entity_id e); private: using comp_index = std::type_index; using comp_to_entities_set = std::unordered_map; using entity_to_index = std::unordered_map; using comp_to_entity_dict = std::unordered_map; using comp_to_storage = std::unordered_map>; using comp_to_reusable_ids = std::unordered_map>; comp_to_entities_set entities_dict; comp_to_entity_dict indices_dict; comp_to_storage storage_dict; comp_to_reusable_ids reusable_id_queues; }; 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 callback; }; std::vector systems; }; class world final { public: entity_id make_entity(); void destroy_entity(entity_id e); bool is_alive(entity_id e) const; template bool has(entity_id e) const; template T& get(entity_id e); template void set(entity_id e); template void set(entity_id e, const T&... comps); template void remove(entity_id e); template 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 inline bool component_storage::has(entity_id e) const { if(entities_dict.contains(typeid(T))) { return entities_dict.at(typeid(T)).contains(e); } return false; } template inline T& component_storage::get(entity_id e) { if(!has(e)) { throw std::runtime_error( std::format("Entity #{} doesn't have {}", e, typeid(T).name())); } auto index = indices_dict[typeid(T)].at(e); auto* ptr = reinterpret_cast(&storage_dict[typeid(T)][0]); ptr += index; return *ptr; } template inline T& world::get(entity_id e) { return storage.get(e); } template inline void component_storage::set(entity_id e, const T& comp) { entities_dict[typeid(T)].emplace(e); auto& storage = storage_dict[typeid(T)]; auto& reusable_ids = reusable_id_queues[typeid(T)]; if(reusable_ids.empty()) { size_t T_size = sizeof(T); size_t old_size = storage.size(); storage.resize(old_size + T_size); void* ptr = &storage[0] + old_size; new(ptr) T(comp); indices_dict[typeid(T)][e] = old_size / T_size; return; } auto index = reusable_ids.front(); reusable_ids.pop(); auto ptr = reinterpret_cast(&storage[0]); new(ptr + index) T(comp); indices_dict[typeid(T)][e] = index; } template inline void component_storage::set(entity_id e) { set(e, T{}); } template inline void world::set(entity_id e) { storage.set(e); } template inline void world::set(entity_id e, const T&... comps) { storage.set(e, comps...); } template inline void component_storage::remove(entity_id e) { if(!has(e)) { return; } entities_dict[typeid(T)].erase(e); reusable_id_queues[typeid(T)].push(indices_dict[typeid(T)][e]); indices_dict[typeid(T)].erase(e); } template inline void world::remove(entity_id e) { storage.remove(e); } template inline bool world::has(entity_id e) const { return storage.has(e); } template inline bool component_storage::has(entity_id e) const { return has(e) && has(e) && (has(e) && ...); } template inline void component_storage::set(entity_id e) { set(e, First{}); set(e, Second{}); (set(e, Rest{}), ...); } template 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 inline void component_storage::remove(entity_id e) { remove(e); remove(e); (remove(e), ...); } template inline void world::query(auto&& system) { for(auto e: alive_entities) { if((has(e))) { system(get(e)...); } } } inline void system_scheduler::add_system(float freq, auto&& func) { systems.emplace_back(1.0f / freq, 0.0f, std::forward(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; } } } }; // namespace zecsy