Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmake/gamecoe_config.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
// gamecoe toolkit
#define GAMECOE_USE_LOGCOE @GAMECOE_USE_LOGCOE@
#define GAMECOE_USE_SOUNDCOE @GAMECOE_USE_SOUNDCOE@
#define GAMECOE_USE_TESTCOE @GAMECOE_USE_TESTCOE@

#if GAMECOE_USE_SOUNDCOE
#include <soundcoe_config.hpp>
Expand Down
99 changes: 56 additions & 43 deletions include/gamecoe/entity/component_pool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,100 +4,113 @@
#include <gamecoe/entity/entity.hpp>
#include <gamecoe/entity/sparse_set.hpp>
#include <vector>
#include <optional>
#include <functional>
#include <cassert>

namespace gamecoe
{
class basic_component_pool
{
protected:
sparse_set m_entities;

private:
virtual void do_reserve(std::size_t capacity) = 0;

public:
virtual ~basic_component_pool() = default;
virtual void remove(entity e) = 0;
virtual void clear() = 0;

bool contains(entity e) const noexcept { return m_entities.contains(e); }
std::size_t size() const noexcept { return m_entities.size(); }
bool empty() const noexcept { return m_entities.empty(); }
void reserve(std::size_t capacity) { m_entities.reserve(capacity); do_reserve(capacity); }
};

template <typename T>
class component_pool : private sparse_set
class component_pool : public basic_component_pool
{
std::vector<T> m_components;

public:
using sparse_set::contains;
using sparse_set::size;
using sparse_set::empty;
void do_reserve(std::size_t capacity) override
{
m_components.reserve(capacity);
}

public:
component_pool() noexcept = default;
component_pool(const component_pool&) = delete;
component_pool& operator=(const component_pool&) = delete;
component_pool(component_pool&&) noexcept = default;
component_pool& operator=(component_pool&&) noexcept = default;

~component_pool() = default;
~component_pool() override = default;

void reserve(std::size_t capacity)
void remove(entity e) override
{
sparse_set::reserve(capacity);
m_components.reserve(capacity);
auto index = m_entities.index(e);
if(!index) return;

std::uint32_t i = index.value();
m_entities.erase_at(i);

if (i != m_components.size() - 1)
m_components[i] = std::move(m_components.back());

m_components.pop_back();
}

void clear() override
{
m_entities.clear();
m_components.clear();
}

template <typename... Args>
T& add(const entity &e, Args&&... args)
T& add(entity e, Args&&... args)
{
if(contains(e))
{
assert(false && "component_pool::add(): entity already has this component");
return m_components[sparse_set::index(e).value()];
return m_components[m_entities.index(e).value()];
}

sparse_set::insert(e);
m_entities.insert(e);
m_components.emplace_back(std::forward<Args>(args)...);

return m_components.back();
}

void remove(const entity &e)
{
auto index = sparse_set::index(e);
if(!index) return;

sparse_set::erase(e);

if (index.value() < m_components.size() - 1)
m_components[index.value()] = std::move(m_components.back());

m_components.pop_back();
}

std::optional<std::reference_wrapper<T>> get(const entity &e)
T& get(entity e)
{
auto index = sparse_set::index(e);
if(!index) return std::nullopt;
auto index = m_entities.index(e);
assert(index && "component_pool::get(): entity does not exist in the pool");

return std::ref(m_components[index.value()]);
return m_components[index.value()];
}

std::optional<std::reference_wrapper<const T>> get(const entity &e) const
const T& get(entity e) const
{
auto index = sparse_set::index(e);
if(!index) return std::nullopt;
auto index = m_entities.index(e);
assert(index && "component_pool::get(): entity does not exist in the pool");

return std::cref(m_components[index.value()]);
return m_components[index.value()];
}

template<typename Func>
void for_each(Func &&func)
{
auto size = m_components.size();
for(std::size_t i = 0; i < size; ++i)
func(sparse_set::get_entity_at_index(i), m_components[i]);
func(m_entities.get_entity_at_index(i), m_components[i]);
}

template<typename Func>
void for_each(Func &&func) const
{
auto size = m_components.size();
for(std::size_t i = 0; i < size; ++i)
func(sparse_set::get_entity_at_index(i), m_components[i]);
}

void clear()
{
sparse_set::clear();
m_components.clear();
func(m_entities.get_entity_at_index(i), m_components[i]);
}

// Iterators invalidated by add/remove (dense array reallocation)
Expand Down
146 changes: 146 additions & 0 deletions include/gamecoe/entity/entities.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#pragma once

#include <cstddef>
#include <gamecoe/entity/entity.hpp>
#include <gamecoe/entity/component_pool.hpp>
#include <utility>
#include <vector>
#include <memory>
#include <cstdint>
#include <cassert>

namespace gamecoe
{
class entities
{
static std::uint32_t s_component_id;

std::vector<std::unique_ptr<basic_component_pool>> m_pools;
std::vector<std::uint32_t> m_recycle_ids;
std::vector<std::uint16_t> m_generations;

std::uint32_t m_current_entity_id{0};

// Returns static and unique id for component T
template <typename T>
static std::uint32_t component_id()
{
static std::uint32_t s_componentT_id = s_component_id++;
return s_componentT_id;
}

// Returns a pointer to the realtime type component_pool<T> of the T component, creates a new pool if not exists
template <typename T>
component_pool<T>* get_pool()
{
std::uint32_t comp_id = component_id<T>();

if (comp_id >= m_pools.size()) m_pools.resize(comp_id + 1);
if (!m_pools[comp_id]) m_pools[comp_id] = std::make_unique<component_pool<T>>();

return static_cast<component_pool<T>*>(m_pools[comp_id].get());
}

public:
entities() = default;
entities(const entities&) = delete;
entities(entities&&) = delete;
entities &operator=(const entities&) = delete;
entities &operator=(entities&&) = delete;

~entities() = default;

// Creates an entity (may be recycled id)
entity create();

// Destroy an entity
void destroy(entity e);

// Destroys all entities
void clear();

// Returns if the entity handle given is valid
bool valid(entity e) const;

// Reserves capacity for a number of entities
void reserve(std::size_t capacity);

// Returns the number of alive entities
std::size_t size() const;

// Emplaces a new T component to entity e
template <typename T, typename... Args>
T& add_component(entity e, Args&&... args)
{
assert(valid(e) && "entities::add_component(): entity is not valid");

auto pool = get_pool<T>();
return pool->add(e, std::forward<Args>(args)...);
}

// Returns if entity e has a component T
template <typename T>
bool has_component(entity e) const
{
if (!valid(e)) return false;

std::uint32_t comp_id = component_id<T>();
if (comp_id >= m_pools.size() || !m_pools[comp_id]) return false;

return m_pools[comp_id]->contains(e);
}

// Removes component T from entity e
template <typename T>
void remove_component(entity e)
{
if (!has_component<T>(e)) return;

m_pools[component_id<T>()]->remove(e);
}

// Returns a pointer to component T of entity e (`nullptr` if entity doesn't have T component),
// also note that the pointer may invalidated by any `add_component` call
template <typename T>
T* get_component(entity e)
{
if (!has_component<T>(e)) return nullptr;

auto pool = static_cast<component_pool<T>*>(m_pools[component_id<T>()].get());
return &(pool->get(e));
}

// Returns a pointer to component T of entity e (`nullptr` if entity doesn't have T component),
// also note that the pointer may invalidated by any `add_component` call
template <typename T>
const T* get_component(entity e) const
{
if (!has_component<T>(e)) return nullptr;

auto pool = static_cast<const component_pool<T>*>(m_pools[component_id<T>()].get());
return &(pool->get(e));
}

// Iterates over all entities and their components and run func() on each of them
template<typename T, typename Func>
void for_each(Func &&func)
{
std::uint32_t comp_id = component_id<T>();
if (comp_id >= m_pools.size() || !m_pools[comp_id]) return;

auto pool = static_cast<component_pool<T>*>(m_pools[comp_id].get());
pool->for_each(std::forward<Func>(func));
}

// Iterates over all entities and their components and run func() on each of them
template<typename T, typename Func>
void for_each(Func &&func) const
{
std::uint32_t comp_id = component_id<T>();
if (comp_id >= m_pools.size() || !m_pools[comp_id]) return;

auto pool = static_cast<const component_pool<T>*>(m_pools[comp_id].get());
pool->for_each(std::forward<Func>(func));
}
};
} // namespace gamecoe
14 changes: 6 additions & 8 deletions include/gamecoe/entity/entity.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ namespace gamecoe

static constexpr entity invalid() noexcept { return entity{std::numeric_limits<std::uint32_t>::max()}; }

auto operator<=>(const entity &other) const noexcept = default;
bool operator==(const entity &other) const noexcept = default;

std::uint32_t id() const noexcept { return m_handle >> GEN_BITS; }
std::uint16_t generation() const noexcept { return m_handle & GEN_MASK; }

static entity create(std::uint32_t id, std::uint16_t generation)
{
assert(id <= MAX_ENTITIES && "entity::create(): entity id exceeds maximum");
Expand All @@ -27,14 +33,6 @@ namespace gamecoe
// [ 20-bit id ][12-bit generation]
return entity((id << GEN_BITS) | generation);
}

auto operator<=>(const entity &other) const noexcept = default;
bool operator==(const entity &other) const noexcept = default;

bool valid() const noexcept { return *this != invalid(); }

std::uint32_t id() const noexcept { return m_handle >> GEN_BITS; }
std::uint16_t generation() const noexcept { return m_handle & GEN_MASK; }

private:
std::uint32_t m_handle;
Expand Down
Loading
Loading