From a012aff7f8a4ea936dfeacc277e766aa307d80c0 Mon Sep 17 00:00:00 2001 From: bog Date: Mon, 30 Oct 2023 21:25:40 +0100 Subject: [PATCH] :sparkles: basic ecs functions. --- meson.build | 5 ++ src/BaseComponent.cpp | 13 +++ src/BaseComponent.hpp | 21 +++++ src/BaseSystem.cpp | 19 +++++ src/BaseSystem.hpp | 25 ++++++ src/ECS.cpp | 59 ++++++++++++++ src/ECS.hpp | 40 +++++++++ src/Game.cpp | 1 - src/Game.hpp | 5 ++ src/Script.cpp | 183 ++++++++++++++++++++++++++++++++++++++++++ src/Script.hpp | 52 ++++++++++++ src/commons.hpp | 3 + 12 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 src/BaseComponent.cpp create mode 100644 src/BaseComponent.hpp create mode 100644 src/BaseSystem.cpp create mode 100644 src/BaseSystem.hpp create mode 100644 src/ECS.cpp create mode 100644 src/ECS.hpp diff --git a/meson.build b/meson.build index 49d9e8e..4b697c4 100644 --- a/meson.build +++ b/meson.build @@ -29,6 +29,11 @@ executable('d2d', 'src/Game.cpp', 'src/Scene.cpp', + # ecs + 'src/ECS.cpp', + 'src/BaseComponent.cpp', + 'src/BaseSystem.cpp', + # graphics 'src/Shader.cpp', 'src/Shape.cpp', diff --git a/src/BaseComponent.cpp b/src/BaseComponent.cpp new file mode 100644 index 0000000..828a855 --- /dev/null +++ b/src/BaseComponent.cpp @@ -0,0 +1,13 @@ +#include "BaseComponent.hpp" + +namespace d2 +{ + /*explicit*/ BaseComponent::BaseComponent(size_t id) + : m_id { id } + { + } + + /*virtual*/ BaseComponent::~BaseComponent() + { + } +} diff --git a/src/BaseComponent.hpp b/src/BaseComponent.hpp new file mode 100644 index 0000000..45191ba --- /dev/null +++ b/src/BaseComponent.hpp @@ -0,0 +1,21 @@ +#ifndef d2_BASECOMPONENT_HPP +#define d2_BASECOMPONENT_HPP + +#include "commons.hpp" + +namespace d2 +{ + class BaseComponent + { + public: + explicit BaseComponent(size_t id); + virtual ~BaseComponent(); + + size_t id() const { return m_id; } + + private: + size_t m_id; + }; +} + +#endif diff --git a/src/BaseSystem.cpp b/src/BaseSystem.cpp new file mode 100644 index 0000000..2024529 --- /dev/null +++ b/src/BaseSystem.cpp @@ -0,0 +1,19 @@ +#include "BaseSystem.hpp" + +namespace d2 +{ + /*explicit*/ BaseSystem::BaseSystem(size_t id) + : m_id { id } + { + } + + /*virtual*/ BaseSystem::~BaseSystem() + { + } + + size_t BaseSystem::add_signature(BaseComponent const& component) + { + m_signature |= (1 << component.id()); + return m_signature; + } +} diff --git a/src/BaseSystem.hpp b/src/BaseSystem.hpp new file mode 100644 index 0000000..d288c78 --- /dev/null +++ b/src/BaseSystem.hpp @@ -0,0 +1,25 @@ +#ifndef d2_BASESYSTEM_HPP +#define d2_BASESYSTEM_HPP + +#include "commons.hpp" +#include "src/BaseComponent.hpp" + +namespace d2 +{ + class BaseSystem + { + public: + explicit BaseSystem(size_t id); + virtual ~BaseSystem(); + + size_t id() const { return m_id; } + size_t signature() const { return m_signature; } + size_t add_signature(BaseComponent const& component); + + private: + size_t m_id = 0; + size_t m_signature = 0; + }; +} + +#endif diff --git a/src/ECS.cpp b/src/ECS.cpp new file mode 100644 index 0000000..a2413f6 --- /dev/null +++ b/src/ECS.cpp @@ -0,0 +1,59 @@ +#include "ECS.hpp" + +namespace d2 +{ + /*explicit*/ ECS::ECS() + { + } + + /*virtual*/ ECS::~ECS() + { + } + + size_t ECS::spawn(std::vector>& components) + { + size_t entity = m_entity_counter; + + for (auto& component: components) + { + m_entities[entity][component->id()] = std::move(component); + } + + m_entity_counter++; + + return entity; + } + + BaseComponent const& ECS::getc(size_t entity, + size_t component) const + { + return *m_entities.at(entity).at(component).get(); + } + + BaseComponent& ECS::getc(size_t entity, + size_t component) + { + return *m_entities.at(entity).at(component).get(); + } + + size_t ECS::esig(size_t entity) const + { + size_t sig = 0; + + for (size_t i=0; iid()); + } + } + + return sig; + } + + bool ECS::sig_match(size_t system_signature, + size_t entity_signature) const + { + return (system_signature & entity_signature) == system_signature; + } +} diff --git a/src/ECS.hpp b/src/ECS.hpp new file mode 100644 index 0000000..e8f1b95 --- /dev/null +++ b/src/ECS.hpp @@ -0,0 +1,40 @@ +#ifndef d2_ECS_HPP +#define d2_ECS_HPP + +#include "commons.hpp" +#include "BaseComponent.hpp" + +namespace d2 +{ + constexpr size_t MAX_ENTITIES = 1024; + constexpr size_t MAX_COMPONENTS = 256; + + class ECS + { + public: + explicit ECS(); + virtual ~ECS(); + + size_t spawn(std::vector>& components); + + size_t entity_count() const { return m_entity_counter; } + + BaseComponent const& getc(size_t entity, + size_t component) const; + + BaseComponent& getc(size_t entity, + size_t component); + + size_t esig(size_t entity) const; + bool sig_match(size_t system_signature, + size_t entity_signature) const; + + private: + size_t m_entity_counter = 0; + std::array, + MAX_COMPONENTS>, + MAX_ENTITIES> m_entities; + }; +} + +#endif diff --git a/src/Game.cpp b/src/Game.cpp index c622c41..f5ba4db 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -179,5 +179,4 @@ namespace d2 SDL_GetMouseState(nullptr, &y); return y; } - } diff --git a/src/Game.hpp b/src/Game.hpp index 57c34db..c72bbbb 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -5,6 +5,7 @@ #include "commons.hpp" #include "Renderer.hpp" #include "Scene.hpp" +#include "ECS.hpp" namespace d2 { @@ -18,6 +19,9 @@ namespace d2 unsigned height); virtual ~Game(); + ECS const& ecs() const { return m_ecs; } + ECS& ecs() { return m_ecs; } + void run(); void queue_draw(std::unique_ptr obj); @@ -39,6 +43,7 @@ namespace d2 std::unique_ptr m_renderer; std::vector> m_draw_queue; std::vector> m_scenes; + ECS m_ecs; }; template diff --git a/src/Script.cpp b/src/Script.cpp index da36b56..d14501e 100644 --- a/src/Script.cpp +++ b/src/Script.cpp @@ -1,10 +1,27 @@ #include "Script.hpp" +#include "libguile/list.h" +#include namespace d2 { + // Script + // ------ + /*static*/ std::unique_ptr Script::game = nullptr; /*static*/ glm::vec4 Script::color = {0.0f, 0.0f, 0.0f, 255.0f}; + // ECS + // ---- + + /*static*/ std::unordered_map> + ScriptComponent::components; + + /*static*/ size_t ScriptSystem::system_id = 0; + /*static*/ std::unordered_map> + ScriptSystem::systems; + + /*static*/ size_t ScriptComponent::comp_id = 0; + /*static*/ void Script::init() { scm_init_guile(); @@ -41,6 +58,27 @@ namespace d2 scm_c_define_gsubr("rootdir", 0, 0, 1, reinterpret_cast(&fn_rootdir)); + // ECS + // ---------- + scm_c_define_gsubr("world:system", 0, 0, 1, + reinterpret_cast(&fn_world__system)); + scm_c_define_gsubr("world:component", 0, 0, 1, + reinterpret_cast(&fn_world__component)); + scm_c_define_gsubr("world:spawn", 0, 0, 1, + reinterpret_cast(&fn_world__spawn)); + scm_c_define_gsubr("world:esig", 1, 0, 0, + reinterpret_cast(&fn_world__esig)); + scm_c_define_gsubr("world:ssig", 1, 0, 0, + reinterpret_cast(&fn_world__ssig)); + scm_c_define_gsubr("world:getc", 3, 0, 0, + reinterpret_cast(&fn_world__getc)); + scm_c_define_gsubr("world:setc!", 4, 0, 0, + reinterpret_cast(&fn_world__setc)); + scm_c_define_gsubr("world:match?", 2, 0, 0, + reinterpret_cast(&fn_world__match)); + scm_c_define_gsubr("world:process", 1, 0, 0, + reinterpret_cast(&fn_world__process)); + } /*static*/ void Script::terminate() @@ -202,4 +240,149 @@ namespace d2 return scm_from_locale_string(path.string().c_str()); } + + /*static*/ SCM Script::fn_world__system(SCM scm_args) + { + size_t len = scm_to_uint64(scm_length(scm_args)); + SCM function = scm_list_ref(scm_args, scm_from_uint64(0)); + auto system = std::make_unique(function); + + for (size_t i=1; iadd_signature(*ScriptComponent::components[value]); + } + + size_t id = system->id(); + ScriptSystem::systems[id] = std::move(system); + + return scm_from_uint64(id); + } + + /*static*/ SCM Script::fn_world__component(SCM scm_args) + { + auto component = + std::make_unique(ScriptComponent::comp_id); + ScriptComponent::comp_id++; + + size_t len = scm_to_uint64(scm_length(scm_args)); + + for (size_t i=0; iadd(name, value); + } + + size_t id = component->id(); + + ScriptComponent::components[id] = std::move(component); + return scm_from_uint64(id); + } + + /*static*/ SCM Script::fn_world__spawn(SCM scm_args) + { + size_t len = scm_to_uint64(scm_length(scm_args)); + std::vector> comps; + + for (size_t i=0; i(*model)); + } + + size_t entity = Script::game->ecs().spawn(comps); + + return scm_from_uint64(entity); + } + + /*static*/ SCM Script::fn_world__esig(SCM scm_entity) + { + size_t entity = scm_to_uint64(scm_entity); + size_t sig = Script::game->ecs().esig(entity); + + return scm_from_uint64(sig); + } + + /*static*/ SCM Script::fn_world__ssig(SCM scm_system) + { + size_t id = scm_to_uint64(scm_system); + auto const& system = ScriptSystem::systems[id]; + + size_t signature = system->signature(); + return scm_from_uint64(signature); + } + + /*static*/ SCM + Script::fn_world__getc(SCM scm_entity, SCM scm_comp, SCM scm_field) + { + size_t entity = scm_to_uint64(scm_entity); + size_t comp = scm_to_uint64(scm_comp); + std::string field = + scm_to_locale_string(scm_symbol_to_string(scm_field)); + + auto const& component = Script::game->ecs().getc(entity, comp); + auto& script_comp = static_cast(component); + + if (script_comp.values.find(field) == std::end(script_comp.values)) + { + throw script_error {"Cannot find field '" + field + "'"}; + } + + return script_comp.values.at(field); + } + + /*static*/ SCM + Script::fn_world__setc(SCM scm_entity, SCM scm_comp, SCM scm_field, + SCM scm_value) + { + size_t entity = scm_to_uint64(scm_entity); + size_t comp = scm_to_uint64(scm_comp); + std::string field = + scm_to_locale_string(scm_symbol_to_string(scm_field)); + + auto& component = Script::game->ecs().getc(entity, comp); + auto& script_comp = static_cast(component); + + script_comp.values[field] = scm_value; + + return scm_value; + } + + /*static*/ SCM Script::fn_world__match(SCM scm_system, SCM scm_entity) + { + size_t sys_sig = + ScriptSystem::systems[scm_to_uint64(scm_system)]->signature(); + + size_t entity_sig = + Script::game->ecs().esig(scm_to_uint64(scm_entity)); + + return + scm_from_bool(Script::game->ecs().sig_match(sys_sig, entity_sig)); + } + + /*static*/ SCM Script::fn_world__process(SCM scm_system) + { + auto const& system = ScriptSystem::systems[scm_to_uint64(scm_system)]; + SCM function = system->function; + size_t entities = Script::game->ecs().entity_count(); + + for (size_t i=0; iecs().sig_match(system->signature(), + Script::game->ecs().esig(i))) + { + scm_call_1(function, scm_from_uint64(i)); + } + } + + return SCM_BOOL_T; + } } diff --git a/src/Script.hpp b/src/Script.hpp index 9d0fa8c..b9df0df 100644 --- a/src/Script.hpp +++ b/src/Script.hpp @@ -4,6 +4,8 @@ #include "commons.hpp" #include "Game.hpp" +#include "BaseComponent.hpp" +#include "BaseSystem.hpp" #include "libguile/numbers.h" namespace d2 @@ -31,6 +33,17 @@ namespace d2 static SCM fn_rootdir(SCM scm_args); + static SCM fn_world__system(SCM scm_args); + static SCM fn_world__component(SCM scm_args); + static SCM fn_world__spawn(SCM scm_args); + static SCM fn_world__esig(SCM scm_entity); + static SCM fn_world__ssig(SCM scm_system); + static SCM fn_world__getc(SCM scm_entity, SCM scm_comp, SCM scm_field); + static SCM fn_world__setc(SCM scm_entity, SCM scm_comp, + SCM scm_field, SCM scm_value); + static SCM fn_world__match(SCM scm_system, SCM scm_entity); + static SCM fn_world__process(SCM scm_system); + private: static std::unique_ptr game; static glm::vec4 color; @@ -56,6 +69,45 @@ namespace d2 { } }; + + struct ScriptComponent: public BaseComponent + { + static size_t comp_id; + static std::unordered_map> + components; + + std::unordered_map values; + + explicit ScriptComponent(ScriptComponent const& other) + : BaseComponent(other.id()) + , values { other.values } + { + } + + explicit ScriptComponent(size_t id) + : BaseComponent(id) + { + } + + void add(std::string const& name, SCM scm_value) + { + values.insert({name, scm_value}); + } + }; + + struct ScriptSystem: public BaseSystem + { + static size_t system_id; + static std::unordered_map> + systems; + SCM function; + + explicit ScriptSystem(SCM p_function) + : BaseSystem(ScriptSystem::system_id++) + , function { p_function } + { + } + }; } #endif diff --git a/src/commons.hpp b/src/commons.hpp index 1428d1f..6a9987e 100644 --- a/src/commons.hpp +++ b/src/commons.hpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include "conf.hpp"