Skip to content
Draft
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
54 changes: 54 additions & 0 deletions src/openvic-simulation/ecs/ArchetypeAlias.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#pragma once

#include <cstdint>
#include <functional>
#include <tuple>

namespace OpenVic::ecs {
using archetype_alias_id_t = uint64_t;

template<typename ...Cs>
struct ArchetypeAlias {
using component_tuple = std::tuple<Cs...>;
};

// Helper to hold a component_tuple as a type
template<typename... Ts>
struct archetype_component_list {};

template<typename T>
struct archetype_component_list_extract;

template<typename... Ts>
struct archetype_component_list_extract<std::tuple<Ts...>> {
using type = archetype_component_list<Ts...>;
};

template<template<typename...> class Templ, typename Tuple>
struct archetype_component_list_apply;

template<template<typename...> class Templ, typename... Ts>
struct archetype_component_list_apply<Templ, std::tuple<Ts...>> {
using type = Templ<Ts...>;
};

template<typename Fn, typename A>
concept archetype_function = requires(Fn fn, A::component_tuple tuple) {
std::apply(fn, tuple);
};

template<typename Fn, typename A, typename... Args>
concept archetype_args_function = requires(Fn fn, A::component_tuple tuple, Args... args) {
std::apply(std::bind_front(fn, args...), tuple);
};

template<typename Fn, typename A, template <typename...> typename C>
concept archetype_contained_function = requires(Fn fn, archetype_component_list_apply<C, typename A::component_tuple>::type container) {
fn(container);
};
}

// Usage Example:
// ECS_DEFINE_ARCHETYPE(LeaderArchetype, LeaderTemplate);
#define ECS_DEFINE_ARCHETYPE(Type, ...) \
struct Type : OpenVic::ecs::ArchetypeAlias<__VA_ARGS__> {};
45 changes: 45 additions & 0 deletions src/openvic-simulation/ecs/World.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
#include <cstdint>
#include <memory>
#include <new>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>

#include "openvic-simulation/ecs/Archetype.hpp"
#include "openvic-simulation/ecs/ArchetypeAlias.hpp"
#include "openvic-simulation/ecs/Chunk.hpp"
#include "openvic-simulation/ecs/ChunkPool.hpp"
#include "openvic-simulation/ecs/ChunkView.hpp"
Expand Down Expand Up @@ -88,6 +90,9 @@ namespace OpenVic::ecs {
template<typename... Cs>
EntityID create_entity(Cs&&... values);

template<typename A, typename... Cs>
EntityID create_entity_archetype(Cs&&... values);

void destroy_entity(EntityID id);
bool is_alive(EntityID id) const;

Expand Down Expand Up @@ -130,11 +135,17 @@ namespace OpenVic::ecs {
template<typename... Cs, typename Fn>
void for_each(Fn&& fn);

template<typename A, archetype_function<A> Fn>
void for_each_archetype(Fn&& fn);

// Same, but the function also receives the EntityID. Useful for collecting IDs to
// destroy later (you can't destroy during iteration without invalidating columns).
template<typename... Cs, typename Fn>
void for_each_with_entity(Fn&& fn);

template<typename A, archetype_args_function<A, EntityID> Fn>
void for_each_archetype_with_entity(Fn&& fn);

// Query overloads — match archetypes against Query::require_ids and reject any that
// overlap Query::exclude_ids. The lambda must accept C&... matching the call site's
// `Cs...` template arguments (the exclude-set isn't reflected in the call signature).
Expand All @@ -153,6 +164,9 @@ namespace OpenVic::ecs {
template<typename... Cs, typename Fn>
void for_each_chunk(Fn&& fn);

template<typename A, archetype_contained_function<A, ChunkView> Fn>
void for_each_archetype_chunk(Fn&& fn);

template<typename... Cs, typename Fn>
void for_each_chunk(Query const& query, Fn&& fn);

Expand Down Expand Up @@ -469,6 +483,13 @@ namespace OpenVic::ecs {
return eid;
}

template<typename A, typename... Cs>
EntityID World::create_entity_archetype(Cs&&... values) {
// TODO: should probably assign the archetype for later checks, but that's runtime behavior
static_assert(std::is_same_v<std::tuple<std::remove_cvref_t<Cs>...>, typename A::component_tuple>, "create_entity_archetype requires the archetype's components to match");
return create_entity(std::forward<Cs>(values)...);
}

template<typename C>
C* World::get_component(EntityID id) {
if (!is_alive(id)) {
Expand Down Expand Up @@ -762,6 +783,14 @@ namespace OpenVic::ecs {
for_each<Cs...>(q, std::forward<Fn>(fn));
}

template<typename A, archetype_function<A> Fn>
void World::for_each_archetype(Fn&& fn) {
// TODO: should probably validate the archetype, deceptively applies to other archetypes otherwise, but that's a runtime check
[this, fn]<typename... Cs>(archetype_component_list<Cs...>) {
for_each<Cs...>(fn);
}(typename archetype_component_list_extract<typename A::component_tuple>::type {});
}

template<typename... Cs, typename Fn>
void World::for_each_with_entity(Fn&& fn) {
static_assert(sizeof...(Cs) > 0, "for_each_with_entity requires at least one component");
Expand All @@ -771,6 +800,14 @@ namespace OpenVic::ecs {
for_each_with_entity<Cs...>(q, std::forward<Fn>(fn));
}

template<typename A, archetype_args_function<A, EntityID> Fn>
void World::for_each_archetype_with_entity(Fn&& fn) {
// TODO: should probably validate the archetype, deceptively applies to other archetypes otherwise, but that's a runtime check
[this, fn]<typename... Cs>(archetype_component_list<Cs...>) {
for_each_with_entity<Cs...>(fn);
}(typename archetype_component_list_extract<typename A::component_tuple>::type {});
}

template<typename... Cs, typename Fn>
void World::for_each(Query const& query, Fn&& fn) {
static_assert(sizeof...(Cs) > 0, "for_each requires at least one component");
Expand Down Expand Up @@ -828,6 +865,14 @@ namespace OpenVic::ecs {
for_each_chunk<Cs...>(q, std::forward<Fn>(fn));
}

template<typename A, archetype_contained_function<A, ChunkView> Fn>
void World::for_each_archetype_chunk(Fn&& fn) {
// TODO: should probably validate the archetype, deceptively applies to other archetypes otherwise, but that's a runtime check
[this, fn]<typename... Cs>(archetype_component_list<Cs...>) {
for_each_chunk<Cs...>(fn);
}(typename archetype_component_list_extract<typename A::component_tuple>::type {});
}

template<typename... Cs, typename Fn>
void World::for_each_chunk(Query const& query, Fn&& fn) {
static_assert(sizeof...(Cs) > 0, "for_each_chunk requires at least one component");
Expand Down
Loading