#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 entity final { public: entity(class world* w, entity_id id); entity() = default; operator entity_id() const; bool is_alive() const; template bool has() const; template T& get(); template void set(); template void set(const T&... comps); template void remove(); private: entity_id id = 0; class world* w = nullptr; }; class component_storage { 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 world final { public: entity 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(Callable&& system); private: entities_set alive_entities; entity_id entity_counter = 0; component_storage storage; }; inline entity::entity(class world* w, entity_id id): w(w), id(id) { } inline entity::operator entity_id() const { return id; } inline bool entity::is_alive() const { return w && w->is_alive(id); } inline entity world::make_entity() { auto id = ++entity_counter; alive_entities.emplace(id); return entity(this, 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 bool entity::has() const { return w->has(id); } template inline T& world::get(entity_id e) { return storage.get(e); } template inline T& entity::get() { return w->get(id); } template inline void entity::set() { (set(T{}), ...); } 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 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 entity::set(const T&... comps) { w->set(id, 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 void entity::remove() { w->remove(id); } 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(Callable&& system) { for(auto e: alive_entities) { if((has(e))) { system(get(e)...); } } } };