2025-02-14 21:35:40 +03:00
|
|
|
#pragma once
|
2025-02-20 01:42:06 +03:00
|
|
|
#include <algorithm>
|
2025-02-20 17:49:49 +03:00
|
|
|
#include <bitset>
|
2025-02-18 22:44:21 +03:00
|
|
|
#include <concepts>
|
2025-02-14 21:35:40 +03:00
|
|
|
#include <cstdint>
|
2025-02-16 20:39:41 +03:00
|
|
|
#include <cstdlib>
|
2025-02-14 22:18:00 +03:00
|
|
|
#include <format>
|
2025-02-16 21:18:39 +03:00
|
|
|
#include <set>
|
2025-02-14 22:27:52 +03:00
|
|
|
#include <stdexcept>
|
2025-02-18 22:27:54 +03:00
|
|
|
#include <type_traits>
|
2025-02-14 23:42:01 +03:00
|
|
|
#include <unordered_map>
|
2025-02-17 22:35:43 +03:00
|
|
|
#include <vector>
|
2025-02-14 21:35:40 +03:00
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
#ifndef ZECSY_MAX_COMPONENTS
|
|
|
|
#define ZECSY_MAX_COMPONENTS 32
|
|
|
|
#endif // !ZECSY_MAX_COMPONENTS
|
|
|
|
|
2025-02-16 21:18:39 +03:00
|
|
|
namespace zecsy
|
2025-02-14 21:35:40 +03:00
|
|
|
{
|
|
|
|
using entity_id = uint64_t;
|
2025-02-15 18:24:18 +03:00
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<typename... T>
|
|
|
|
concept Component = []
|
|
|
|
{
|
2025-02-18 22:44:21 +03:00
|
|
|
static_assert((std::is_default_constructible_v<T> && ...),
|
|
|
|
"Should have a default constructor");
|
2025-02-18 22:27:54 +03:00
|
|
|
static_assert((std::is_trivially_copyable_v<T> && ...),
|
2025-02-18 22:44:21 +03:00
|
|
|
"Should be trivially copyable");
|
2025-02-18 22:27:54 +03:00
|
|
|
static_assert((std::is_trivially_destructible_v<T> && ...),
|
2025-02-18 22:44:21 +03:00
|
|
|
"Should be trivially destructible");
|
|
|
|
static_assert((std::is_standard_layout_v<T> && ...),
|
|
|
|
"Should have standard layout");
|
2025-02-18 22:27:54 +03:00
|
|
|
return true;
|
|
|
|
}();
|
|
|
|
|
2025-02-19 22:50:21 +03:00
|
|
|
class world final
|
2025-02-14 23:42:01 +03:00
|
|
|
{
|
|
|
|
public:
|
2025-02-19 22:50:21 +03:00
|
|
|
entity_id make_entity();
|
|
|
|
void destroy_entity(entity_id e);
|
|
|
|
bool is_alive(entity_id e) const;
|
2025-02-20 02:39:24 +03:00
|
|
|
size_t components_in_entity(entity_id e) const;
|
|
|
|
|
|
|
|
size_t entity_count() const;
|
|
|
|
size_t total_component_count() const;
|
|
|
|
size_t archetype_count() const;
|
|
|
|
|
|
|
|
template<Component T>
|
|
|
|
size_t component_count();
|
2025-02-19 22:50:21 +03:00
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component T>
|
2025-02-14 23:42:01 +03:00
|
|
|
bool has(entity_id e) const;
|
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component First, Component Second, Component... Rest>
|
2025-02-15 01:13:51 +03:00
|
|
|
bool has(entity_id e) const;
|
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component T>
|
2025-02-14 23:42:01 +03:00
|
|
|
T& get(entity_id e);
|
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component T>
|
2025-02-15 01:47:12 +03:00
|
|
|
void set(entity_id e);
|
2025-02-16 21:18:39 +03:00
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component T>
|
2025-02-15 01:47:12 +03:00
|
|
|
void set(entity_id e, const T& comp);
|
2025-02-16 21:18:39 +03:00
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component First, Component Second, Component... Rest>
|
2025-02-15 01:47:12 +03:00
|
|
|
void set(entity_id e);
|
2025-02-15 00:23:01 +03:00
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component First, Component Second, Component... Rest>
|
2025-02-16 21:18:39 +03:00
|
|
|
void set(entity_id e, const First& comp0, const Second& comp1,
|
|
|
|
const Rest&... rest_comps);
|
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component T>
|
2025-02-15 00:23:01 +03:00
|
|
|
void remove(entity_id e);
|
2025-02-16 21:18:39 +03:00
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component First, Component Second, Component... Rest>
|
2025-02-15 01:55:05 +03:00
|
|
|
void remove(entity_id e);
|
2025-02-16 21:18:39 +03:00
|
|
|
|
2025-02-19 22:50:21 +03:00
|
|
|
template<Component... T>
|
2025-02-20 00:17:11 +03:00
|
|
|
void query(std::invocable<entity_id, T&...> auto&& system);
|
2025-02-19 22:50:21 +03:00
|
|
|
|
2025-02-20 02:39:24 +03:00
|
|
|
size_t get_archetypes_checked() const;
|
|
|
|
size_t get_entities_processed() const;
|
|
|
|
|
2025-02-14 23:42:01 +03:00
|
|
|
private:
|
2025-02-17 22:35:43 +03:00
|
|
|
using comp_id = size_t;
|
2025-02-20 17:49:49 +03:00
|
|
|
using zecsy_bits = std::bitset<ZECSY_MAX_COMPONENTS>;
|
|
|
|
|
|
|
|
std::unordered_map<entity_id, zecsy_bits> entity_to_comps;
|
2025-02-19 23:56:08 +03:00
|
|
|
entity_id entity_counter = 0;
|
2025-02-14 23:42:01 +03:00
|
|
|
|
2025-02-20 02:39:24 +03:00
|
|
|
size_t query_archetypes_checked = 0;
|
|
|
|
size_t query_entities_processed = 0;
|
|
|
|
|
2025-02-17 22:35:43 +03:00
|
|
|
struct component_pool
|
2025-02-16 20:39:41 +03:00
|
|
|
{
|
2025-02-17 22:35:43 +03:00
|
|
|
std::vector<uint8_t> data;
|
2025-02-20 01:42:06 +03:00
|
|
|
std::vector<size_t> free_list;
|
2025-02-17 22:35:43 +03:00
|
|
|
std::unordered_map<entity_id, size_t> entity_to_index;
|
2025-02-16 20:39:41 +03:00
|
|
|
};
|
|
|
|
|
2025-02-17 22:35:43 +03:00
|
|
|
std::unordered_map<comp_id, component_pool> pools;
|
2025-02-14 23:42:01 +03:00
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
using archetype_signature = zecsy_bits;
|
2025-02-20 14:36:48 +03:00
|
|
|
using entity_group = std::set<entity_id>;
|
2025-02-20 01:42:06 +03:00
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
std::unordered_map<archetype_signature, entity_group> archetypes;
|
2025-02-20 01:42:06 +03:00
|
|
|
|
|
|
|
template<Component T>
|
|
|
|
static comp_id get_component_id();
|
2025-02-16 21:18:39 +03:00
|
|
|
|
2025-02-17 22:35:43 +03:00
|
|
|
static comp_id next_component_id;
|
2025-02-14 21:35:40 +03:00
|
|
|
};
|
|
|
|
|
2025-02-20 01:42:06 +03:00
|
|
|
template<Component T>
|
|
|
|
inline world::comp_id world::get_component_id()
|
|
|
|
{
|
|
|
|
static comp_id id = next_component_id++;
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2025-02-19 22:50:21 +03:00
|
|
|
inline world::comp_id world::next_component_id = 0;
|
|
|
|
|
2025-02-20 02:39:24 +03:00
|
|
|
inline size_t world::components_in_entity(entity_id e) const
|
|
|
|
{
|
2025-02-20 17:49:49 +03:00
|
|
|
return entity_to_comps.contains(e) ? entity_to_comps.at(e).count() : 0;
|
2025-02-20 02:39:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
inline size_t world::entity_count() const
|
|
|
|
{
|
2025-02-20 17:49:49 +03:00
|
|
|
return entity_to_comps.size();
|
2025-02-20 02:39:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
inline size_t world::total_component_count() const
|
|
|
|
{
|
|
|
|
size_t count = 0;
|
|
|
|
|
|
|
|
for(const auto& [id, pool]: pools)
|
|
|
|
{
|
|
|
|
count += pool.entity_to_index.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline size_t world::archetype_count() const
|
|
|
|
{
|
|
|
|
return archetypes.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
template<Component T>
|
|
|
|
inline size_t world::component_count()
|
|
|
|
{
|
|
|
|
const comp_id id = get_component_id<T>();
|
|
|
|
const auto it = pools.find(id);
|
|
|
|
return it != pools.end() ? it->second.entity_to_index.size() : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline size_t world::get_archetypes_checked() const
|
|
|
|
{
|
|
|
|
return query_archetypes_checked;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline size_t world::get_entities_processed() const
|
|
|
|
{
|
|
|
|
return query_entities_processed;
|
|
|
|
}
|
|
|
|
|
2025-02-19 22:50:21 +03:00
|
|
|
inline entity_id world::make_entity()
|
|
|
|
{
|
|
|
|
auto id = ++entity_counter;
|
2025-02-20 01:42:06 +03:00
|
|
|
entity_to_comps[id] = {};
|
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
archetype_signature key;
|
2025-02-20 01:42:06 +03:00
|
|
|
auto& group = archetypes[key];
|
2025-02-20 14:36:48 +03:00
|
|
|
group.emplace(id);
|
2025-02-20 01:42:06 +03:00
|
|
|
|
2025-02-19 22:50:21 +03:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void world::destroy_entity(entity_id e)
|
|
|
|
{
|
2025-02-20 17:49:49 +03:00
|
|
|
auto archetype = entity_to_comps[e];
|
2025-02-20 01:42:06 +03:00
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
auto& group = archetypes[archetype];
|
2025-02-20 14:36:48 +03:00
|
|
|
group.erase(e);
|
2025-02-20 01:42:06 +03:00
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
if(archetypes[archetype].empty())
|
2025-02-20 01:42:06 +03:00
|
|
|
{
|
2025-02-20 17:49:49 +03:00
|
|
|
archetypes.erase(archetype);
|
2025-02-20 01:42:06 +03:00
|
|
|
}
|
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
for(int id = 0; id < ZECSY_MAX_COMPONENTS; ++id)
|
2025-02-19 23:56:08 +03:00
|
|
|
{
|
2025-02-20 17:49:49 +03:00
|
|
|
if(archetype.test(id))
|
|
|
|
{
|
|
|
|
auto& pool = pools[id];
|
|
|
|
auto index = pool.entity_to_index[e];
|
|
|
|
pool.entity_to_index.erase(e);
|
|
|
|
pool.free_list.emplace_back(index);
|
|
|
|
}
|
2025-02-19 23:56:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
entity_to_comps.erase(e);
|
2025-02-19 22:50:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
inline bool world::is_alive(entity_id e) const
|
|
|
|
{
|
2025-02-20 17:49:49 +03:00
|
|
|
return entity_to_comps.contains(e);
|
2025-02-19 22:50:21 +03:00
|
|
|
}
|
2025-02-14 23:42:01 +03:00
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component T>
|
2025-02-19 22:50:21 +03:00
|
|
|
inline bool world::has(entity_id e) const
|
2025-02-14 23:42:01 +03:00
|
|
|
{
|
2025-02-19 23:56:08 +03:00
|
|
|
if(entity_to_comps.contains(e))
|
2025-02-14 23:42:01 +03:00
|
|
|
{
|
2025-02-20 17:49:49 +03:00
|
|
|
return entity_to_comps.at(e).test(get_component_id<T>());
|
2025-02-14 23:42:01 +03:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component T>
|
2025-02-19 22:50:21 +03:00
|
|
|
inline T& world::get(entity_id e)
|
2025-02-14 23:42:01 +03:00
|
|
|
{
|
2025-02-17 22:35:43 +03:00
|
|
|
auto id = get_component_id<T>();
|
2025-02-14 23:42:01 +03:00
|
|
|
if(!has<T>(e))
|
|
|
|
{
|
2025-02-16 21:18:39 +03:00
|
|
|
throw std::runtime_error(
|
|
|
|
std::format("Entity #{} doesn't have {}", e, typeid(T).name()));
|
2025-02-14 23:42:01 +03:00
|
|
|
}
|
|
|
|
|
2025-02-17 22:35:43 +03:00
|
|
|
auto& pool = pools.at(id);
|
|
|
|
auto index = pool.entity_to_index.at(e);
|
|
|
|
return *reinterpret_cast<T*>(&pool.data[index * sizeof(T)]);
|
2025-02-14 23:42:01 +03:00
|
|
|
}
|
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component T>
|
2025-02-19 22:50:21 +03:00
|
|
|
inline void world::set(entity_id e, const T& comp)
|
2025-02-14 23:42:01 +03:00
|
|
|
{
|
2025-02-17 22:35:43 +03:00
|
|
|
auto id = get_component_id<T>();
|
|
|
|
auto& pool = pools[id];
|
2025-02-14 23:42:01 +03:00
|
|
|
|
2025-02-17 22:35:43 +03:00
|
|
|
size_t index;
|
|
|
|
if(!pool.free_list.empty())
|
2025-02-15 00:23:01 +03:00
|
|
|
{
|
2025-02-17 22:50:53 +03:00
|
|
|
index = pool.free_list.back();
|
|
|
|
pool.free_list.pop_back();
|
2025-02-17 22:35:43 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
index = pool.data.size() / sizeof(T);
|
|
|
|
pool.data.resize(pool.data.size() + sizeof(T));
|
2025-02-15 00:23:01 +03:00
|
|
|
}
|
|
|
|
|
2025-02-17 22:35:43 +03:00
|
|
|
new(&pool.data[index * sizeof(T)]) T(comp);
|
|
|
|
pool.entity_to_index[e] = index;
|
2025-02-19 23:56:08 +03:00
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
auto& archetype = entity_to_comps[e];
|
|
|
|
auto old_archetype = archetype;
|
|
|
|
archetype.set(id);
|
2025-02-20 01:42:06 +03:00
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
auto& group = archetypes[old_archetype];
|
|
|
|
group.erase(e);
|
2025-02-20 01:42:06 +03:00
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
if(archetypes[old_archetype].empty())
|
|
|
|
{
|
|
|
|
archetypes.erase(old_archetype);
|
2025-02-20 01:42:06 +03:00
|
|
|
}
|
2025-02-20 17:49:49 +03:00
|
|
|
|
|
|
|
archetypes[archetype].emplace(e);
|
|
|
|
}
|
2025-02-16 21:18:39 +03:00
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component T>
|
2025-02-19 22:50:21 +03:00
|
|
|
inline void world::set(entity_id e)
|
2025-02-15 22:33:02 +03:00
|
|
|
{
|
|
|
|
set(e, T{});
|
|
|
|
}
|
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component T>
|
2025-02-19 22:50:21 +03:00
|
|
|
inline void world::remove(entity_id e)
|
2025-02-15 00:23:01 +03:00
|
|
|
{
|
2025-02-20 17:49:49 +03:00
|
|
|
if(!has<T>(e))
|
2025-02-15 00:23:01 +03:00
|
|
|
{
|
2025-02-20 17:49:49 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-02-20 01:52:30 +03:00
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
auto id = get_component_id<T>();
|
|
|
|
auto& archetype = entity_to_comps[e];
|
2025-02-20 01:42:06 +03:00
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
auto& old_group = archetypes[archetype];
|
|
|
|
old_group.erase(e);
|
2025-02-20 02:54:40 +03:00
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
if(old_group.empty())
|
|
|
|
{
|
|
|
|
archetypes.erase(archetype);
|
2025-02-20 01:52:30 +03:00
|
|
|
}
|
2025-02-20 17:49:49 +03:00
|
|
|
|
|
|
|
archetype.reset(id);
|
|
|
|
archetypes[archetype].emplace(e);
|
|
|
|
|
|
|
|
auto& pool = pools[id];
|
|
|
|
auto index = pool.entity_to_index[e];
|
|
|
|
pool.free_list.push_back(index);
|
|
|
|
pool.entity_to_index.erase(e);
|
2025-02-15 01:13:51 +03:00
|
|
|
}
|
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component First, Component Second, Component... Rest>
|
2025-02-19 22:50:21 +03:00
|
|
|
inline bool world::has(entity_id e) const
|
2025-02-15 01:13:51 +03:00
|
|
|
{
|
|
|
|
return has<First>(e) && has<Second>(e) && (has<Rest>(e) && ...);
|
|
|
|
}
|
2025-02-16 21:18:39 +03:00
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component First, Component Second, Component... Rest>
|
2025-02-19 22:50:21 +03:00
|
|
|
inline void world::set(entity_id e)
|
2025-02-15 01:47:12 +03:00
|
|
|
{
|
|
|
|
set(e, First{});
|
|
|
|
set(e, Second{});
|
|
|
|
(set(e, Rest{}), ...);
|
|
|
|
}
|
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component First, Component Second, Component... Rest>
|
2025-02-19 22:50:21 +03:00
|
|
|
inline void world::set(entity_id e, const First& comp0, const Second& comp1,
|
|
|
|
const Rest&... rest_comps)
|
2025-02-15 01:47:12 +03:00
|
|
|
{
|
|
|
|
set(e, comp0);
|
|
|
|
set(e, comp1);
|
|
|
|
(set(e, rest_comps), ...);
|
|
|
|
}
|
2025-02-15 01:55:05 +03:00
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component First, Component Second, Component... Rest>
|
2025-02-19 22:50:21 +03:00
|
|
|
inline void world::remove(entity_id e)
|
2025-02-15 01:55:05 +03:00
|
|
|
{
|
|
|
|
remove<First>(e);
|
|
|
|
remove<Second>(e);
|
|
|
|
(remove<Rest>(e), ...);
|
|
|
|
}
|
2025-02-15 20:13:38 +03:00
|
|
|
|
2025-02-18 22:27:54 +03:00
|
|
|
template<Component... T>
|
2025-02-20 00:17:11 +03:00
|
|
|
inline void world::query(std::invocable<entity_id, T&...> auto&& system)
|
2025-02-17 22:35:43 +03:00
|
|
|
{
|
2025-02-20 17:49:49 +03:00
|
|
|
/*std::vector<comp_id> required = {get_component_id<T>()...};*/
|
|
|
|
|
|
|
|
archetype_signature required;
|
|
|
|
|
|
|
|
(required.set(get_component_id<T>()), ...);
|
2025-02-20 01:42:06 +03:00
|
|
|
|
2025-02-20 02:39:24 +03:00
|
|
|
query_archetypes_checked = 0;
|
|
|
|
query_entities_processed = 0;
|
|
|
|
|
2025-02-20 01:42:06 +03:00
|
|
|
for(const auto& [archetype_key, entities]: archetypes)
|
2025-02-17 22:35:43 +03:00
|
|
|
{
|
2025-02-20 02:39:24 +03:00
|
|
|
query_archetypes_checked++;
|
|
|
|
|
2025-02-20 17:49:49 +03:00
|
|
|
if((archetype_key & required) == required)
|
2025-02-17 22:35:43 +03:00
|
|
|
{
|
2025-02-20 02:39:24 +03:00
|
|
|
query_entities_processed += entities.size();
|
2025-02-20 01:42:06 +03:00
|
|
|
for(entity_id e: entities)
|
|
|
|
{
|
|
|
|
system(e, get<T>(e)...);
|
|
|
|
}
|
2025-02-17 22:35:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-02-16 21:18:39 +03:00
|
|
|
}; // namespace zecsy
|