zecsy/zecsy.hpp

322 lines
7.9 KiB
C++
Raw Normal View History

2025-02-14 21:35:40 +03:00
#pragma once
2025-02-16 20:39:41 +03:00
#include <algorithm>
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 20:39:41 +03:00
#include <functional>
2025-02-15 00:23:01 +03:00
#include <queue>
2025-02-14 22:27:52 +03:00
#include <stdexcept>
2025-02-14 23:42:01 +03:00
#include <typeindex>
#include <unordered_map>
2025-02-15 17:52:33 +03:00
#include <unordered_set>
#include <set>
2025-02-14 21:35:40 +03:00
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<entity_id>;
2025-02-14 21:35:40 +03:00
2025-02-15 22:33:02 +03:00
class component_storage final
2025-02-14 23:42:01 +03:00
{
public:
template<typename T>
bool has(entity_id e) const;
2025-02-15 01:13:51 +03:00
template<typename First, typename Second, typename... Rest>
bool has(entity_id e) const;
2025-02-14 23:42:01 +03:00
template<typename T>
T& get(entity_id e);
template<typename T>
2025-02-15 01:47:12 +03:00
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);
2025-02-15 00:23:01 +03:00
2025-02-15 01:47:12 +03:00
template<typename First, typename Second, typename... Rest>
void set(entity_id e, const First& comp0, const Second& comp1,
const Rest&... rest_comps);
2025-02-15 00:23:01 +03:00
template<typename T>
void remove(entity_id e);
2025-02-15 01:55:05 +03:00
template<typename First, typename Second, typename... Rest>
void remove(entity_id e);
2025-02-14 23:42:01 +03:00
private:
using comp_index = std::type_index;
2025-02-15 17:52:33 +03:00
using comp_to_entities_set = std::unordered_map<comp_index, entities_set>;
2025-02-14 23:42:01 +03:00
using entity_to_index = std::unordered_map<entity_id, size_t>;
using comp_to_entity_dict = std::unordered_map<comp_index,
entity_to_index>;
using comp_to_storage = std::unordered_map<comp_index,
std::vector<uint8_t>>;
2025-02-15 00:23:01 +03:00
using comp_to_reusable_ids = std::unordered_map<comp_index,
std::queue<size_t>>;
2025-02-15 17:52:33 +03:00
comp_to_entities_set entities_dict;
comp_to_entity_dict indices_dict;
2025-02-14 23:42:01 +03:00
comp_to_storage storage_dict;
2025-02-15 00:23:01 +03:00
comp_to_reusable_ids reusable_id_queues;
2025-02-14 23:42:01 +03:00
};
2025-02-16 20:39:41 +03:00
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;
};
2025-02-14 21:35:40 +03:00
class world final
{
public:
2025-02-15 22:33:02 +03:00
entity_id make_entity();
void destroy_entity(entity_id e);
2025-02-14 21:35:40 +03:00
bool is_alive(entity_id e) const;
2025-02-14 23:42:01 +03:00
2025-02-15 01:13:51 +03:00
template<typename... T>
2025-02-14 23:42:01 +03:00
bool has(entity_id e) const;
template<typename T>
T& get(entity_id e);
2025-02-15 01:47:12 +03:00
template<typename... T>
void set(entity_id e);
template<typename... T>
void set(entity_id e, const T&... comps);
2025-02-15 00:23:01 +03:00
2025-02-15 01:55:05 +03:00
template<typename... T>
2025-02-15 00:23:01 +03:00
void remove(entity_id e);
2025-02-15 20:13:38 +03:00
template<typename... T, typename Callable>
void query(Callable&& system);
2025-02-14 21:35:40 +03:00
private:
2025-02-15 17:52:33 +03:00
entities_set alive_entities;
2025-02-14 21:35:40 +03:00
entity_id entity_counter = 0;
2025-02-14 23:42:01 +03:00
component_storage storage;
2025-02-14 21:35:40 +03:00
};
2025-02-15 22:33:02 +03:00
inline entity_id world::make_entity()
2025-02-14 22:18:00 +03:00
{
2025-02-14 22:27:52 +03:00
auto id = ++entity_counter;
2025-02-15 17:52:33 +03:00
alive_entities.emplace(id);
2025-02-14 22:18:00 +03:00
2025-02-15 22:33:02 +03:00
return id;
2025-02-14 22:18:00 +03:00
}
inline void world::destroy_entity(entity_id e)
{
2025-02-15 17:52:33 +03:00
alive_entities.erase(e);
}
2025-02-14 21:35:40 +03:00
inline bool world::is_alive(entity_id e) const
{
2025-02-15 17:52:33 +03:00
return alive_entities.contains(e);
2025-02-14 21:35:40 +03:00
}
2025-02-14 23:42:01 +03:00
template<typename T>
inline bool component_storage::has(entity_id e) const
{
2025-02-15 17:52:33 +03:00
if(entities_dict.contains(typeid(T)))
2025-02-14 23:42:01 +03:00
{
2025-02-15 17:52:33 +03:00
return entities_dict.at(typeid(T)).contains(e);
2025-02-14 23:42:01 +03:00
}
return false;
}
template<typename T>
inline T& component_storage::get(entity_id e)
{
if(!has<T>(e))
{
throw std::runtime_error(std::format("Entity #{} doesn't have {}",
e, typeid(T).name()));
}
2025-02-15 17:52:33 +03:00
auto index = indices_dict[typeid(T)].at(e);
2025-02-14 23:42:01 +03:00
auto* ptr = reinterpret_cast<T*>(&storage_dict[typeid(T)][0]);
ptr += index;
return *ptr;
}
template<typename T>
inline T& world::get(entity_id e)
{
return storage.get<T>(e);
}
template<typename T>
2025-02-14 23:56:01 +03:00
inline void component_storage::set(entity_id e, const T& comp)
2025-02-14 23:42:01 +03:00
{
2025-02-15 17:52:33 +03:00
entities_dict[typeid(T)].emplace(e);
2025-02-14 23:42:01 +03:00
auto& storage = storage_dict[typeid(T)];
2025-02-15 00:23:01 +03:00
auto& reusable_ids = reusable_id_queues[typeid(T)];
2025-02-14 23:42:01 +03:00
2025-02-15 00:23:01 +03:00
if(reusable_ids.empty())
{
size_t T_size = sizeof(T);
size_t old_size = storage.size();
2025-02-14 23:56:01 +03:00
2025-02-15 00:23:01 +03:00
storage.resize(old_size + T_size);
void* ptr = &storage[0] + old_size;
new(ptr) T(comp);
2025-02-15 17:52:33 +03:00
indices_dict[typeid(T)][e] = old_size / T_size;
2025-02-15 00:23:01 +03:00
return;
}
auto index = reusable_ids.front();
reusable_ids.pop();
auto ptr = reinterpret_cast<T*>(&storage[0]);
new(ptr + index) T(comp);
2025-02-15 17:52:33 +03:00
indices_dict[typeid(T)][e] = index;
2025-02-14 23:42:01 +03:00
}
2025-02-15 01:47:12 +03:00
2025-02-15 22:33:02 +03:00
template<typename T>
inline void component_storage::set(entity_id e)
{
set(e, T{});
}
2025-02-15 01:47:12 +03:00
template<typename... T>
inline void world::set(entity_id e)
{
storage.set<T...>(e);
}
2025-02-14 23:42:01 +03:00
2025-02-15 01:47:12 +03:00
template<typename... T>
inline void world::set(entity_id e, const T&... comps)
2025-02-14 23:42:01 +03:00
{
2025-02-15 01:47:12 +03:00
storage.set(e, comps...);
2025-02-14 23:42:01 +03:00
}
2025-02-15 00:23:01 +03:00
template<typename T>
inline void component_storage::remove(entity_id e)
{
if(!has<T>(e))
{
return;
}
2025-02-15 17:52:33 +03:00
entities_dict[typeid(T)].erase(e);
reusable_id_queues[typeid(T)].push(indices_dict[typeid(T)][e]);
indices_dict[typeid(T)].erase(e);
2025-02-15 00:23:01 +03:00
}
2025-02-15 01:55:05 +03:00
template<typename... T>
2025-02-15 00:23:01 +03:00
inline void world::remove(entity_id e)
{
2025-02-15 01:55:05 +03:00
storage.remove<T...>(e);
2025-02-15 00:23:01 +03:00
}
2025-02-15 01:13:51 +03:00
template<typename... T>
inline bool world::has(entity_id e) const
{
return storage.has<T...>(e);
}
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) && ...);
}
2025-02-15 01:47:12 +03:00
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), ...);
}
2025-02-15 01:55:05 +03:00
template<typename First, typename Second, typename... Rest>
inline void component_storage::remove(entity_id e)
{
remove<First>(e);
remove<Second>(e);
(remove<Rest>(e), ...);
}
2025-02-15 20:13:38 +03:00
template<typename... T, typename Callable>
inline void world::query(Callable&& system)
{
for(auto e: alive_entities)
{
if((has<T...>(e)))
{
system(get<T>(e)...);
}
}
}
2025-02-16 20:39:41 +03:00
inline void system_scheduler::add_system(float freq, auto&& func)
{
systems.push_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;
}
}
}
2025-02-14 21:35:40 +03:00
};